Fix FAB not returning after Snackbar dismissal
Caused by the fact that sometimes a removed view does
not trigger a onDependentViewChanged() dispatch. As
a removal doesn't really fit that method, I've added
a onDependentViewRemoved() call to Behavior.
BUG: 21799255
Change-Id: Ib86713a38928dfdbbfcf419e44bafe8023e5a8ee
diff --git a/design/api/current.txt b/design/api/current.txt
index 27b0a30..0e638cf 100644
--- a/design/api/current.txt
+++ b/design/api/current.txt
@@ -127,6 +127,7 @@
method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, V, android.view.View);
method public android.support.v4.view.WindowInsetsCompat onApplyWindowInsets(android.support.design.widget.CoordinatorLayout, V, android.support.v4.view.WindowInsetsCompat);
method public boolean onDependentViewChanged(android.support.design.widget.CoordinatorLayout, V, android.view.View);
+ method public void onDependentViewRemoved(android.support.design.widget.CoordinatorLayout, V, android.view.View);
method public boolean onInterceptTouchEvent(android.support.design.widget.CoordinatorLayout, V, android.view.MotionEvent);
method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, V, int);
method public boolean onMeasureChild(android.support.design.widget.CoordinatorLayout, V, int, int, int, int);
@@ -179,6 +180,7 @@
ctor public FloatingActionButton.Behavior();
method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, android.support.design.widget.FloatingActionButton, android.view.View);
method public boolean onDependentViewChanged(android.support.design.widget.CoordinatorLayout, android.support.design.widget.FloatingActionButton, android.view.View);
+ method public void onDependentViewRemoved(android.support.design.widget.CoordinatorLayout, android.support.design.widget.FloatingActionButton, android.view.View);
method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, android.support.design.widget.FloatingActionButton, int);
}
diff --git a/design/src/android/support/design/widget/CoordinatorLayout.java b/design/src/android/support/design/widget/CoordinatorLayout.java
index e2081b7..8675e6d 100644
--- a/design/src/android/support/design/widget/CoordinatorLayout.java
+++ b/design/src/android/support/design/widget/CoordinatorLayout.java
@@ -149,6 +149,8 @@
private boolean mDrawStatusBarBackground;
private Drawable mStatusBarBackground;
+ private OnHierarchyChangeListener mOnHierarchyChangeListener;
+
private final NestedScrollingParentHelper mNestedScrollingParentHelper =
new NestedScrollingParentHelper(this);
@@ -181,6 +183,12 @@
if (INSETS_HELPER != null) {
INSETS_HELPER.setupForWindowInsets(this, new ApplyInsetsListener());
}
+ super.setOnHierarchyChangeListener(new HierarchyChangeListener());
+ }
+
+ @Override
+ public void setOnHierarchyChangeListener(OnHierarchyChangeListener onHierarchyChangeListener) {
+ mOnHierarchyChangeListener = onHierarchyChangeListener;
}
@Override
@@ -1110,6 +1118,19 @@
}
}
+ void dispatchDependentViewRemoved(View removedChild) {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ final Behavior b = lp.getBehavior();
+
+ if (b != null && b.layoutDependsOn(this, child, removedChild)) {
+ b.onDependentViewRemoved(this, child, removedChild);
+ }
+ }
+ }
+
/**
* Allows the caller to manually dispatch
* {@link Behavior#onDependentViewChanged(CoordinatorLayout, View, View)} to the associated
@@ -1734,6 +1755,23 @@
}
/**
+ * Respond to a child's dependent view being removed.
+ *
+ * <p>This method is called after a dependent view has been removed from the parent.
+ * A Behavior may use this method to appropriately update the child view in response.</p>
+ *
+ * <p>A view's dependency is determined by
+ * {@link #layoutDependsOn(CoordinatorLayout, android.view.View, android.view.View)} or
+ * if {@code child} has set another view as it's anchor.</p>
+ *
+ * @param parent the parent view of the given child
+ * @param child the child view to manipulate
+ * @param dependency the dependent view that has been removed
+ */
+ public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {
+ }
+
+ /**
* Determine whether the given child view should be considered dirty.
*
* <p>If a property determined by the Behavior such as other dependent views would change,
@@ -2457,6 +2495,24 @@
}
}
+ final class HierarchyChangeListener implements OnHierarchyChangeListener {
+ @Override
+ public void onChildViewAdded(View parent, View child) {
+ if (mOnHierarchyChangeListener != null) {
+ mOnHierarchyChangeListener.onChildViewAdded(parent, child);
+ }
+ }
+
+ @Override
+ public void onChildViewRemoved(View parent, View child) {
+ dispatchDependentViewRemoved(child);
+
+ if (mOnHierarchyChangeListener != null) {
+ mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
+ }
+ }
+ }
+
@Override
protected void onRestoreInstanceState(Parcelable state) {
final SavedState ss = (SavedState) state;
diff --git a/design/src/android/support/design/widget/FloatingActionButton.java b/design/src/android/support/design/widget/FloatingActionButton.java
index 42af049..b275174 100644
--- a/design/src/android/support/design/widget/FloatingActionButton.java
+++ b/design/src/android/support/design/widget/FloatingActionButton.java
@@ -351,6 +351,19 @@
return false;
}
+ @Override
+ public void onDependentViewRemoved(CoordinatorLayout parent, FloatingActionButton child,
+ View dependency) {
+ if (dependency instanceof Snackbar.SnackbarLayout) {
+ // If the removed view is a SnackbarLayout, we will animate back to our normal
+ // position
+ ViewCompat.animate(child)
+ .translationY(0f)
+ .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
+ .setListener(null);
+ }
+ }
+
private boolean updateFabVisibility(CoordinatorLayout parent,
AppBarLayout appBarLayout, FloatingActionButton child) {
final CoordinatorLayout.LayoutParams lp =
@@ -389,18 +402,8 @@
if (translationY != mTranslationY) {
// First, cancel any current animation
ViewCompat.animate(fab).cancel();
-
- if (Math.abs(translationY - mTranslationY) == snackbar.getHeight()) {
- // If we're travelling by the height of the Snackbar then we probably need to
- // animate to the value
- ViewCompat.animate(fab)
- .translationY(translationY)
- .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
- .setListener(null);
- } else {
- // Else we'll set use setTranslationY
- ViewCompat.setTranslationY(fab, translationY);
- }
+ // Else we'll set use setTranslationY
+ ViewCompat.setTranslationY(fab, translationY);
mTranslationY = translationY;
}
}