public static <V extends View> BottomSheetBehavior<V> from(V view) { LayoutParams params = view.getLayoutParams(); if (params instanceof CoordinatorLayout.LayoutParams) { Behavior behavior = ((CoordinatorLayout.LayoutParams) params).getBehavior(); if (behavior instanceof BottomSheetBehavior) { return (BottomSheetBehavior) behavior; } throw new IllegalArgumentException("The view is not associated with BottomSheetBehavior"); } throw new IllegalArgumentException("The view is not a child of CoordinatorLayout"); }
@Test public void testDependentViewRemoved() throws Throwable { final CoordinatorLayout col = activityTestRule.getActivity().mCoordinatorLayout; // Add two views, A & B, where B depends on A final View viewA = new View(col.getContext()); final View viewB = new View(col.getContext()); final CoordinatorLayout.LayoutParams lpB = col.generateDefaultLayoutParams(); final CoordinatorLayout.Behavior behavior = spy(new DependentBehavior(viewA)); lpB.setBehavior(behavior); activityTestRule.runOnUiThread( new Runnable() { @Override public void run() { col.addView(viewA); col.addView(viewB, lpB); } }); getInstrumentation().waitForIdleSync(); // Now remove view A activityTestRule.runOnUiThread( new Runnable() { @Override public void run() { col.removeView(viewA); } }); // And assert that View B's Behavior was called appropriately verify(behavior, times(1)).onDependentViewRemoved(col, viewB, viewA); }
@Test public void testGetDependenciesAfterDependentViewRemoved() throws Throwable { final CoordinatorLayout col = activityTestRule.getActivity().mCoordinatorLayout; // Add two views, A & B, where B depends on A final View viewA = new View(col.getContext()); final View viewB = new View(col.getContext()); final CoordinatorLayout.LayoutParams lpB = col.generateDefaultLayoutParams(); final CoordinatorLayout.Behavior behavior = new DependentBehavior(viewA) { @Override public void onDependentViewRemoved( CoordinatorLayout parent, View child, View dependency) { parent.getDependencies(child); } }; lpB.setBehavior(behavior); // Now add views activityTestRule.runOnUiThread( new Runnable() { @Override public void run() { col.addView(viewA); col.addView(viewB, lpB); } }); // Wait for a layout getInstrumentation().waitForIdleSync(); // Now remove view A, which will trigger onDependentViewRemoved() on view B's behavior activityTestRule.runOnUiThread( new Runnable() { @Override public void run() { col.removeView(viewA); } }); }
/** * A utility function to get the {@link ExpandableBehavior} attached to the {@code view}. * * @param view The {@link View} that the {@link ExpandableBehavior} is attached to. * @param klass The expected {@link Class} of the attached {@link ExpandableBehavior}. * @return The {@link ExpandableBehavior} attached to the {@code view}. */ public static <T extends ExpandableBehavior> T from(View view, Class<T> klass) { ViewGroup.LayoutParams params = view.getLayoutParams(); if (!(params instanceof CoordinatorLayout.LayoutParams)) { throw new IllegalArgumentException("The view is not a child of CoordinatorLayout"); } CoordinatorLayout.Behavior<?> behavior = ((CoordinatorLayout.LayoutParams) params).getBehavior(); if (!(behavior instanceof ExpandableBehavior)) { throw new IllegalArgumentException("The view is not associated with ExpandableBehavior"); } return klass.cast(behavior); }
/** * @return The {@link Behavior} that controls how children of this class animate together. */ public Behavior<?> getBehavior() { return mBehavior; }
@Test @TargetApi(21) @SdkSuppress(minSdkVersion = 21) public void testSetFitSystemWindows() throws Throwable { final CoordinatorLayout col = activityTestRule.getActivity().mCoordinatorLayout; final View view = new View(col.getContext()); // Create a mock which calls the default impl of onApplyWindowInsets() final CoordinatorLayout.Behavior<View> mockBehavior = mock(CoordinatorLayout.Behavior.class); doCallRealMethod() .when(mockBehavior) .onApplyWindowInsets(same(col), same(view), any(WindowInsetsCompat.class)); // Assert that the CoL is currently not set to fitSystemWindows assertFalse(col.getFitsSystemWindows()); // Now add a view with our mocked behavior to the CoordinatorLayout view.setFitsSystemWindows(true); activityTestRule.runOnUiThread( new Runnable() { @Override public void run() { final CoordinatorLayout.LayoutParams lp = col.generateDefaultLayoutParams(); lp.setBehavior(mockBehavior); col.addView(view, lp); } }); getInstrumentation().waitForIdleSync(); // Now request some insets and wait for the pass to happen activityTestRule.runOnUiThread( new Runnable() { @Override public void run() { col.requestApplyInsets(); } }); getInstrumentation().waitForIdleSync(); // Verify that onApplyWindowInsets() has not been called verify(mockBehavior, never()) .onApplyWindowInsets(same(col), same(view), any(WindowInsetsCompat.class)); // Now enable fits system windows and wait for a pass to happen activityTestRule.runOnUiThread( new Runnable() { @Override public void run() { col.setFitsSystemWindows(true); } }); getInstrumentation().waitForIdleSync(); // Verify that onApplyWindowInsets() has been called with some insets verify(mockBehavior, atLeastOnce()) .onApplyWindowInsets(same(col), same(view), any(WindowInsetsCompat.class)); }
@Test public void testDependentViewChanged() throws Throwable { final CoordinatorLayout col = activityTestRule.getActivity().mCoordinatorLayout; // Add two views, A & B, where B depends on A final View viewA = new View(col.getContext()); final CoordinatorLayout.LayoutParams lpA = col.generateDefaultLayoutParams(); lpA.width = 100; lpA.height = 100; final View viewB = new View(col.getContext()); final CoordinatorLayout.LayoutParams lpB = col.generateDefaultLayoutParams(); lpB.width = 100; lpB.height = 100; final CoordinatorLayout.Behavior behavior = spy(new DependentBehavior(viewA)); lpB.setBehavior(behavior); activityTestRule.runOnUiThread( new Runnable() { @Override public void run() { col.addView(viewA, lpA); col.addView(viewB, lpB); } }); getInstrumentation().waitForIdleSync(); // Reset the Behavior since onDependentViewChanged may have already been called as part of // any layout/draw passes already reset(behavior); // Now offset view A activityTestRule.runOnUiThread( new Runnable() { @Override public void run() { ViewCompat.offsetLeftAndRight(viewA, 20); ViewCompat.offsetTopAndBottom(viewA, 20); } }); getInstrumentation().waitForIdleSync(); // And assert that view B's Behavior was called appropriately verify(behavior, times(1)).onDependentViewChanged(col, viewB, viewA); }
@Test public void testDodgeInsetBeforeLayout() throws Throwable { final CoordinatorLayout col = activityTestRule.getActivity().mCoordinatorLayout; // Add a dummy view, which will be used to trigger a hierarchy change. final View dummy = new View(col.getContext()); activityTestRule.runOnUiThread( new Runnable() { @Override public void run() { col.addView(dummy); } }); // Wait for a layout. getInstrumentation().waitForIdleSync(); final View dodge = new View(col.getContext()); final CoordinatorLayout.LayoutParams lpDodge = col.generateDefaultLayoutParams(); lpDodge.dodgeInsetEdges = Gravity.BOTTOM; lpDodge.setBehavior( new Behavior() { @Override public boolean getInsetDodgeRect(CoordinatorLayout parent, View child, Rect rect) { // Any non-empty rect is fine here. rect.set(0, 0, 10, 10); return true; } }); activityTestRule.runOnUiThread( new Runnable() { @Override public void run() { col.addView(dodge, lpDodge); // Ensure the new view is in the list of children. int heightSpec = MeasureSpec.makeMeasureSpec(col.getHeight(), MeasureSpec.EXACTLY); int widthSpec = MeasureSpec.makeMeasureSpec(col.getWidth(), MeasureSpec.EXACTLY); col.measure(widthSpec, heightSpec); // Force a hierarchy change. col.removeView(dummy); } }); // Wait for a layout. getInstrumentation().waitForIdleSync(); }
@Test public void testNestedScrollingDispatchesToBehavior() throws Throwable { final CoordinatorLayoutActivity activity = activityTestRule.getActivity(); final CoordinatorLayout col = activity.mCoordinatorLayout; // Now create a view and add it to the CoordinatorLayout with the spy behavior, // along with a NestedScrollView final ImageView imageView = new ImageView(activity); final CoordinatorLayout.Behavior behavior = spy(new NestedScrollingBehavior()); activityTestRule.runOnUiThread( new Runnable() { @Override public void run() { LayoutInflater.from(activity).inflate(R.layout.include_nestedscrollview, col, true); CoordinatorLayout.LayoutParams clp = new CoordinatorLayout.LayoutParams(200, 200); clp.setBehavior(behavior); col.addView(imageView, clp); } }); // Now vertically swipe up on the NSV, causing nested scrolling to occur onView(withId(R.id.nested_scrollview)).perform(swipeUp()); // Verify that the Behavior's onStartNestedScroll was called once verify(behavior, times(1)) .onStartNestedScroll( eq(col), // parent eq(imageView), // child any(View.class), // target any(View.class), // direct child target any(int.class)); // axes // Verify that the Behavior's onNestedScrollAccepted was called once verify(behavior, times(1)) .onNestedScrollAccepted( eq(col), // parent eq(imageView), // child any(View.class), // target any(View.class), // direct child target any(int.class)); // axes // Verify that the Behavior's onNestedPreScroll was called at least once verify(behavior, atLeastOnce()) .onNestedPreScroll( eq(col), // parent eq(imageView), // child any(View.class), // target any(int.class), // dx any(int.class), // dy any(int[].class)); // consumed // Verify that the Behavior's onNestedScroll was called at least once verify(behavior, atLeastOnce()) .onNestedScroll( eq(col), // parent eq(imageView), // child any(View.class), // target any(int.class), // dx consumed any(int.class), // dy consumed any(int.class), // dx unconsumed any(int.class)); // dy unconsumed // Verify that the Behavior's onStopNestedScroll was called once verify(behavior, times(1)) .onStopNestedScroll( eq(col), // parent eq(imageView), // child any(View.class)); // target }
@Test public void testNestedScrollingDispatchingToBehaviorWithGoneView() throws Throwable { final CoordinatorLayoutActivity activity = activityTestRule.getActivity(); final CoordinatorLayout col = activity.mCoordinatorLayout; // Now create a GONE view and add it to the CoordinatorLayout with the spy behavior, // along with a NestedScrollView final ImageView imageView = new ImageView(activity); imageView.setVisibility(View.GONE); final CoordinatorLayout.Behavior behavior = spy(new NestedScrollingBehavior()); activityTestRule.runOnUiThread( new Runnable() { @Override public void run() { LayoutInflater.from(activity).inflate(R.layout.include_nestedscrollview, col, true); CoordinatorLayout.LayoutParams clp = new CoordinatorLayout.LayoutParams(200, 200); clp.setBehavior(behavior); col.addView(imageView, clp); } }); // Now vertically swipe up on the NSV, causing nested scrolling to occur onView(withId(R.id.nested_scrollview)).perform(swipeUp()); // Verify that the Behavior's onStartNestedScroll was not called verify(behavior, never()) .onStartNestedScroll( eq(col), // parent eq(imageView), // child any(View.class), // target any(View.class), // direct child target any(int.class)); // axes // Verify that the Behavior's onNestedScrollAccepted was not called verify(behavior, never()) .onNestedScrollAccepted( eq(col), // parent eq(imageView), // child any(View.class), // target any(View.class), // direct child target any(int.class)); // axes // Verify that the Behavior's onNestedPreScroll was not called verify(behavior, never()) .onNestedPreScroll( eq(col), // parent eq(imageView), // child any(View.class), // target any(int.class), // dx any(int.class), // dy any(int[].class)); // consumed // Verify that the Behavior's onNestedScroll was not called verify(behavior, never()) .onNestedScroll( eq(col), // parent eq(imageView), // child any(View.class), // target any(int.class), // dx consumed any(int.class), // dy consumed any(int.class), // dx unconsumed any(int.class)); // dy unconsumed // Verify that the Behavior's onStopNestedScroll was not called verify(behavior, never()) .onStopNestedScroll( eq(col), // parent eq(imageView), // child any(View.class)); // target }
@Test public void testNestedScrollingTriggeringDependentViewChanged() throws Throwable { final CoordinatorLayoutActivity activity = activityTestRule.getActivity(); final CoordinatorLayout col = activity.mCoordinatorLayout; // First a NestedScrollView to trigger nested scrolling final View scrollView = LayoutInflater.from(activity).inflate(R.layout.include_nestedscrollview, col, false); // Now create a View and Behavior which depend on the scrollview final ImageView dependentView = new ImageView(activity); final CoordinatorLayout.Behavior dependentBehavior = spy(new DependentBehavior(scrollView)); // Finally a view which accepts nested scrolling in the CoordinatorLayout final ImageView nestedScrollAwareView = new ImageView(activity); activityTestRule.runOnUiThread( new Runnable() { @Override public void run() { // First add the ScrollView col.addView(scrollView); // Now add the view which depends on the scrollview CoordinatorLayout.LayoutParams clp = new CoordinatorLayout.LayoutParams(200, 200); clp.setBehavior(dependentBehavior); col.addView(dependentView, clp); // Now add the nested scrolling aware view clp = new CoordinatorLayout.LayoutParams(200, 200); clp.setBehavior(new NestedScrollingBehavior()); col.addView(nestedScrollAwareView, clp); } }); // Wait for any layouts, and reset the Behavior so that the call counts are 0 getInstrumentation().waitForIdleSync(); reset(dependentBehavior); // Now vertically swipe up on the NSV, causing nested scrolling to occur onView(withId(R.id.nested_scrollview)).perform(swipeUp()); // Verify that the Behavior's onDependentViewChanged is not called due to the // nested scroll verify(dependentBehavior, never()) .onDependentViewChanged( eq(col), // parent eq(dependentView), // child eq(scrollView)); // axes }