Improve CoordinatorLayout's inset edge funtionality

Currently the gravity handling only supports literal
single gravity values which makes it less useful.

Also made the edge detection work with animations by
correctly employing our pre-draw listener.

Tidied up some of the dispatch code in CoordinatorLayout
so that we have just one method which dispatches any
events. Means that we don't have to copy code everywhere.

Also added support for the functionality in Snackbar
and FloatingActionButton since that is what most people
will be trying to use it with. Removed our old baked in
functionality from FAB since we can use the new
CoordinatorLayout functionality.

BUG: 30107168

Change-Id: Ib23fa740c4c4f4f733a3894ceb2cc1a2e95b35cd
diff --git a/api/current.txt b/api/current.txt
index fdc62453..a28ee73 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -459,14 +459,17 @@
     ctor public CoordinatorLayout.Behavior();
     ctor public CoordinatorLayout.Behavior(android.content.Context, android.util.AttributeSet);
     method public boolean blocksInteractionBelow(android.support.design.widget.CoordinatorLayout, V);
+    method public boolean getInsetDodgeRect(android.support.design.widget.CoordinatorLayout, V, android.graphics.Rect);
     method public int getScrimColor(android.support.design.widget.CoordinatorLayout, V);
     method public float getScrimOpacity(android.support.design.widget.CoordinatorLayout, V);
     method public static java.lang.Object getTag(android.view.View);
     method public deprecated boolean isDirty(android.support.design.widget.CoordinatorLayout, V);
     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 void onAttachedToLayoutParams(android.support.design.widget.CoordinatorLayout.LayoutParams);
     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 void onDetachedFromLayoutParams();
     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);
@@ -534,10 +537,9 @@
   public static class FloatingActionButton.Behavior extends android.support.design.widget.CoordinatorLayout.Behavior {
     ctor public FloatingActionButton.Behavior();
     ctor public FloatingActionButton.Behavior(android.content.Context, android.util.AttributeSet);
+    method public boolean getInsetDodgeRect(android.support.design.widget.CoordinatorLayout, android.support.design.widget.FloatingActionButton, android.graphics.Rect);
     method public boolean isAutoHideEnabled();
-    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);
     method public void setAutoHideEnabled(boolean);
   }
diff --git a/design/src/android/support/design/widget/CoordinatorLayout.java b/design/src/android/support/design/widget/CoordinatorLayout.java
index 897f0cd..654db04 100644
--- a/design/src/android/support/design/widget/CoordinatorLayout.java
+++ b/design/src/android/support/design/widget/CoordinatorLayout.java
@@ -31,6 +31,8 @@
 import android.os.SystemClock;
 import android.support.annotation.ColorInt;
 import android.support.annotation.DrawableRes;
+import android.support.annotation.IdRes;
+import android.support.annotation.IntDef;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.design.R;
@@ -138,6 +140,15 @@
         }
     };
 
+    private static final int EVENT_PRE_DRAW = 0;
+    private static final int EVENT_NESTED_SCROLL = 1;
+    private static final int EVENT_VIEW_REMOVED = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({EVENT_PRE_DRAW, EVENT_NESTED_SCROLL, EVENT_VIEW_REMOVED})
+    public @interface DispatchChangeEvent {}
+
     static final Comparator<View> TOP_SORTED_CHILDREN_COMPARATOR;
 
     private final List<View> mDependencySortedChildren = new ArrayList<View>();
@@ -878,7 +889,7 @@
      */
     void getChildRect(View child, boolean transform, Rect out) {
         if (child.isLayoutRequested() || child.getVisibility() == View.GONE) {
-            out.set(0, 0, 0, 0);
+            out.setEmpty();
             return;
         }
         if (transform) {
@@ -1168,10 +1179,9 @@
      * the LayoutParams; instead it expects that the layout process will always reconstruct
      * the proper positioning.
      *
-     * @param fromNestedScroll true if this is being called from one of the nested scroll methods,
-     *                         false if run as part of the pre-draw step.
+     * @param type the type of event which has caused this call
      */
-    void dispatchOnDependentViewChanged(final boolean fromNestedScroll) {
+    final void onChildViewsChanged(@DispatchChangeEvent final int type) {
         final int layoutDirection = ViewCompat.getLayoutDirection(this);
         final int childCount = mDependencySortedChildren.size();
         final Rect inset = mTempRect4;
@@ -1189,40 +1199,44 @@
                 }
             }
 
+            // Get the current draw rect of the view
+            final Rect drawRect = mTempRect1;
+            getChildRect(child, true, drawRect);
+
             // Accumulate inset sizes
-            if (lp.insetEdge > 0) {
+            if (lp.insetEdge != Gravity.NO_GRAVITY && !drawRect.isEmpty()) {
                 final int absInsetEdge = GravityCompat.getAbsoluteGravity(
                         lp.insetEdge, layoutDirection);
-                switch (absInsetEdge) {
+                switch (absInsetEdge & Gravity.VERTICAL_GRAVITY_MASK) {
                     case Gravity.TOP:
-                        inset.top = Math.max(inset.top, child.getBottom());
+                        inset.top = Math.max(inset.top, drawRect.bottom);
                         break;
                     case Gravity.BOTTOM:
-                        inset.bottom = Math.max(inset.bottom, getHeight() - child.getTop());
+                        inset.bottom = Math.max(inset.bottom, getHeight() - drawRect.top);
                         break;
+                }
+                switch (absInsetEdge & Gravity.HORIZONTAL_GRAVITY_MASK) {
                     case Gravity.LEFT:
-                        inset.left = Math.max(inset.left, child.getRight());
+                        inset.left = Math.max(inset.left, drawRect.right);
                         break;
                     case Gravity.RIGHT:
-                        inset.right = Math.max(inset.right, getWidth() - child.getLeft());
+                        inset.right = Math.max(inset.right, getWidth() - drawRect.left);
                         break;
                 }
             }
 
             // Dodge inset edges if necessary
-            if (lp.dodgeInsetEdges > 0) {
+            if (lp.dodgeInsetEdges != Gravity.NO_GRAVITY) {
                 offsetChildByInset(child, inset, layoutDirection);
             }
 
             // Did it change? if not continue
-            final Rect oldRect = mTempRect1;
-            final Rect newRect = mTempRect2;
-            getLastChildRect(child, oldRect);
-            getChildRect(child, true, newRect);
-            if (oldRect.equals(newRect)) {
+            final Rect lastDrawRect = mTempRect2;
+            getLastChildRect(child, lastDrawRect);
+            if (lastDrawRect.equals(drawRect)) {
                 continue;
             }
-            recordLastChildRect(child, newRect);
+            recordLastChildRect(child, drawRect);
 
             // Update any behavior-dependent views for the change
             for (int j = i + 1; j < childCount; j++) {
@@ -1231,16 +1245,28 @@
                 final Behavior b = checkLp.getBehavior();
 
                 if (b != null && b.layoutDependsOn(this, checkChild, child)) {
-                    if (!fromNestedScroll && checkLp.getChangedAfterNestedScroll()) {
-                        // If this is not from a nested scroll and we have already been changed
+                    if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) {
+                        // If this is from a pre-draw and we have already been changed
                         // from a nested scroll, skip the dispatch and reset the flag
                         checkLp.resetChangedAfterNestedScroll();
                         continue;
                     }
 
-                    final boolean handled = b.onDependentViewChanged(this, checkChild, child);
+                    final boolean handled;
+                    switch (type) {
+                        case EVENT_VIEW_REMOVED:
+                            // EVENT_VIEW_REMOVED means that we need to dispatch
+                            // onDependentViewRemoved() instead
+                            b.onDependentViewRemoved(this, checkChild, child);
+                            handled = true;
+                            break;
+                        default:
+                            // Otherwise we dispatch onDependentViewChanged()
+                            handled = b.onDependentViewChanged(this, checkChild, child);
+                            break;
+                    }
 
-                    if (fromNestedScroll) {
+                    if (type == EVENT_NESTED_SCROLL) {
                         // If this is from a nested scroll, set the flag so that we may skip
                         // any resulting onPreDraw dispatch (if needed)
                         checkLp.setChangedAfterNestedScroll(handled);
@@ -1254,36 +1280,39 @@
         LayoutParams lp = (LayoutParams) child.getLayoutParams();
         final int absDodgeInsetEdges = GravityCompat.getAbsoluteGravity(lp.dodgeInsetEdges,
                 layoutDirection);
-        if ((absDodgeInsetEdges & Gravity.TOP) == Gravity.TOP) {
-            int distance = child.getTop() - lp.topMargin - lp.mInsetOffsetY;
-            if (distance < inset.top) {
-                setInsetOffsetY(child, inset.top - distance);
-            } else {
-                setInsetOffsetY(child, 0);
+
+        final Behavior behavior = lp.getBehavior();
+        final Rect rect = mTempRect3;
+        if (behavior != null && behavior.getInsetDodgeRect(this, child, rect)) {
+            // Make sure that it intersects the views bounds
+            rect.intersect(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
+        } else {
+            rect.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
+        }
+
+        switch (absDodgeInsetEdges & Gravity.VERTICAL_GRAVITY_MASK) {
+            case Gravity.TOP: {
+                int distance = rect.top - lp.topMargin - lp.mInsetOffsetY;
+                setInsetOffsetY(child, Math.max(0, inset.top - distance));
+                break;
+            }
+            case Gravity.BOTTOM: {
+                int distance = getHeight() - rect.bottom - lp.bottomMargin + lp.mInsetOffsetY;
+                setInsetOffsetY(child, Math.min(0, distance - inset.bottom));
+                break;
             }
         }
-        if ((absDodgeInsetEdges & Gravity.BOTTOM) == Gravity.BOTTOM) {
-            int distance = getHeight() - child.getBottom() - lp.bottomMargin + lp.mInsetOffsetY;
-            if (distance < inset.bottom) {
-                setInsetOffsetY(child, -inset.bottom + distance);
-            } else {
-                setInsetOffsetY(child, 0);
+
+        switch (absDodgeInsetEdges & Gravity.HORIZONTAL_GRAVITY_MASK) {
+            case Gravity.LEFT: {
+                int distance = rect.left - lp.leftMargin - lp.mInsetOffsetX;
+                setInsetOffsetX(child, Math.max(0, inset.left - distance));
+                break;
             }
-        }
-        if ((absDodgeInsetEdges & Gravity.LEFT) == Gravity.LEFT) {
-            int distance = child.getLeft() - lp.leftMargin - lp.mInsetOffsetX;
-            if (distance < inset.left) {
-                setInsetOffsetX(child, inset.left - distance);
-            } else {
-                setInsetOffsetX(child, 0);
-            }
-        }
-        if ((absDodgeInsetEdges & Gravity.RIGHT) == Gravity.RIGHT) {
-            int distance = getWidth() - child.getRight() - lp.rightMargin + lp.mInsetOffsetX;
-            if (distance < inset.right) {
-                setInsetOffsetX(child, -inset.right + distance);
-            } else {
-                setInsetOffsetX(child, 0);
+            case Gravity.RIGHT: {
+                int distance = getWidth() - rect.right - lp.rightMargin + lp.mInsetOffsetX;
+                setInsetOffsetX(child, Math.min(0, distance - inset.right));
+                break;
             }
         }
     }
@@ -1306,27 +1335,6 @@
         }
     }
 
-    void dispatchDependentViewRemoved(View view) {
-        final int childCount = mDependencySortedChildren.size();
-        boolean viewSeen = false;
-        for (int i = 0; i < childCount; i++) {
-            final View child = mDependencySortedChildren.get(i);
-            if (child == view) {
-                // We've seen our view, which means that any Views after this could be dependent
-                viewSeen = true;
-                continue;
-            }
-            if (viewSeen) {
-                CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)
-                        child.getLayoutParams();
-                CoordinatorLayout.Behavior b = lp.getBehavior();
-                if (b != null && lp.dependsOn(this, child, view)) {
-                    b.onDependentViewRemoved(this, child, view);
-                }
-            }
-        }
-    }
-
     /**
      * Allows the caller to manually dispatch
      * {@link Behavior#onDependentViewChanged(CoordinatorLayout, View, View)} to the associated
@@ -1661,7 +1669,7 @@
         }
 
         if (accepted) {
-            dispatchOnDependentViewChanged(true);
+            onChildViewsChanged(EVENT_NESTED_SCROLL);
         }
     }
 
@@ -1697,7 +1705,7 @@
         consumed[1] = yConsumed;
 
         if (accepted) {
-            dispatchOnDependentViewChanged(true);
+            onChildViewsChanged(EVENT_NESTED_SCROLL);
         }
     }
 
@@ -1720,7 +1728,7 @@
             }
         }
         if (handled) {
-            dispatchOnDependentViewChanged(true);
+            onChildViewsChanged(EVENT_NESTED_SCROLL);
         }
         return handled;
     }
@@ -1753,7 +1761,7 @@
     class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
         @Override
         public boolean onPreDraw() {
-            dispatchOnDependentViewChanged(false);
+            onChildViewsChanged(EVENT_PRE_DRAW);
             return true;
         }
     }
@@ -1817,6 +1825,27 @@
         }
 
         /**
+         * Called when the Behavior has been attached to a LayoutParams instance.
+         *
+         * <p>This will be called after the LayoutParams has been instantiated and can be
+         * modified.</p>
+         *
+         * @param params the LayoutParams instance that this Behavior has been attached to
+         */
+        public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams params) {
+        }
+
+        /**
+         * Called when the Behavior has been detached from its holding LayoutParams instance.
+         *
+         * <p>This will only be called if the Behavior has been explicitly removed from the
+         * LayoutParams instance via {@link LayoutParams#setBehavior(Behavior)}. It will not be
+         * called if the associated view is removed from the CoordinatorLayout or similar.</p>
+         */
+        public void onDetachedFromLayoutParams() {
+        }
+
+        /**
          * Respond to CoordinatorLayout touch events before they are dispatched to child views.
          *
          * <p>Behaviors can use this to monitor inbound touch events until one decides to
@@ -2352,6 +2381,24 @@
         public Parcelable onSaveInstanceState(CoordinatorLayout parent, V child) {
             return BaseSavedState.EMPTY_STATE;
         }
+
+        /**
+         * Called when a view is set to dodge view insets.
+         *
+         * <p>This method allows a behavior to update the rectangle that should be dodged.
+         * The rectangle should be in the parents coordinate system, and within the child's
+         * bounds.</p>
+         *
+         * @param parent the CoordinatorLayout parent of the view this Behavior is
+         *               associated with
+         * @param child  the child view of the CoordinatorLayout this Behavior is associated with
+         * @param rect   the rect to update with the dodge rectangle
+         * @return true the rect was updated, false if we should use the child's bounds
+         */
+        public boolean getInsetDodgeRect(@NonNull CoordinatorLayout parent, @NonNull V child,
+                @NonNull Rect rect) {
+            return false;
+        }
     }
 
     /**
@@ -2433,15 +2480,18 @@
             insetEdge = a.getInt(R.styleable.CoordinatorLayout_Layout_layout_insetEdge, 0);
             dodgeInsetEdges = a.getInt(
                     R.styleable.CoordinatorLayout_Layout_layout_dodgeInsetEdges, 0);
-
             mBehaviorResolved = a.hasValue(
                     R.styleable.CoordinatorLayout_Layout_layout_behavior);
             if (mBehaviorResolved) {
                 mBehavior = parseBehavior(context, attrs, a.getString(
                         R.styleable.CoordinatorLayout_Layout_layout_behavior));
             }
-
             a.recycle();
+
+            if (mBehavior != null) {
+                // If we have a Behavior, dispatch that it has been attached
+                mBehavior.onAttachedToLayoutParams(this);
+            }
         }
 
         public LayoutParams(LayoutParams p) {
@@ -2461,6 +2511,7 @@
          *
          * @return A {@link View#getId() view id} or {@link View#NO_ID} if there is no anchor
          */
+        @IdRes
         public int getAnchorId() {
             return mAnchorId;
         }
@@ -2475,7 +2526,7 @@
          * @param id The {@link View#getId() view id} of the anchor or
          *           {@link View#NO_ID} if there is no anchor
          */
-        public void setAnchorId(int id) {
+        public void setAnchorId(@IdRes int id) {
             invalidateAnchor();
             mAnchorId = id;
         }
@@ -2486,6 +2537,7 @@
          *
          * @return The current behavior or null if no behavior is specified
          */
+        @NonNull
         public Behavior getBehavior() {
             return mBehavior;
         }
@@ -2499,11 +2551,21 @@
          *
          * @param behavior The behavior to set or null for no special behavior
          */
-        public void setBehavior(Behavior behavior) {
+        public void setBehavior(@Nullable Behavior behavior) {
             if (mBehavior != behavior) {
+                if (mBehavior != null) {
+                    // First detach any old behavior
+                    mBehavior.onDetachedFromLayoutParams();
+                }
+
                 mBehavior = behavior;
                 mBehaviorTag = null;
                 mBehaviorResolved = true;
+
+                if (behavior != null) {
+                    // Now dispatch that the Behavior has been attached
+                    behavior.onAttachedToLayoutParams(this);
+                }
             }
         }
 
@@ -2719,11 +2781,19 @@
         /**
          * Checks whether the view with this LayoutParams should dodge the specified view.
          */
-        private boolean shouldDodge(View view, int layoutDirection) {
-            LayoutParams params = (LayoutParams) view.getLayoutParams();
-            int insetEdge = GravityCompat.getAbsoluteGravity(params.insetEdge, layoutDirection);
-            return insetEdge > 0 && (insetEdge & GravityCompat.getAbsoluteGravity(dodgeInsetEdges,
-                            layoutDirection)) == insetEdge;
+        private boolean shouldDodge(View other, int layoutDirection) {
+            LayoutParams otherLp = (LayoutParams) other.getLayoutParams();
+            final int absInset = GravityCompat.getAbsoluteGravity(
+                    otherLp.insetEdge, layoutDirection);
+            if (absInset != Gravity.NO_GRAVITY) {
+                final int absDodge = GravityCompat.getAbsoluteGravity(
+                        dodgeInsetEdges, layoutDirection);
+                return (absInset & Gravity.HORIZONTAL_GRAVITY_MASK)
+                        == (absDodge & Gravity.HORIZONTAL_GRAVITY_MASK)
+                        || (absInset & Gravity.VERTICAL_GRAVITY_MASK)
+                        == (absDodge & Gravity.VERTICAL_GRAVITY_MASK);
+            }
+            return false;
         }
     }
 
@@ -2737,7 +2807,8 @@
 
         @Override
         public void onChildViewRemoved(View parent, View child) {
-            dispatchDependentViewRemoved(child);
+            prepareChildren();
+            onChildViewsChanged(EVENT_VIEW_REMOVED);
 
             if (mOnHierarchyChangeListener != null) {
                 mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
diff --git a/design/src/android/support/design/widget/FloatingActionButton.java b/design/src/android/support/design/widget/FloatingActionButton.java
index 375ecfd..526596c 100644
--- a/design/src/android/support/design/widget/FloatingActionButton.java
+++ b/design/src/android/support/design/widget/FloatingActionButton.java
@@ -38,6 +38,7 @@
 import android.support.v7.widget.AppCompatImageHelper;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
 import android.widget.ImageView;
@@ -525,14 +526,8 @@
      * not cover them.
      */
     public static class Behavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
-        // We only support the FAB <> Snackbar shift movement on Honeycomb and above. This is
-        // because we can use view translation properties which greatly simplifies the code.
-        private static final boolean SNACKBAR_BEHAVIOR_ENABLED = Build.VERSION.SDK_INT >= 11;
-
         private static final boolean AUTO_HIDE_DEFAULT = true;
 
-        private ValueAnimatorCompat mFabTranslationYAnimator;
-        private float mFabTranslationY;
         private Rect mTmpRect;
         private OnVisibilityChangedListener mInternalAutoHideListener;
         private boolean mAutoHideEnabled;
@@ -576,18 +571,18 @@
         }
 
         @Override
-        public boolean layoutDependsOn(CoordinatorLayout parent,
-                FloatingActionButton child, View dependency) {
-            // We're dependent on all SnackbarLayouts (if enabled)
-            return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout;
+        public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams lp) {
+            if (lp.dodgeInsetEdges == Gravity.NO_GRAVITY) {
+                // If the developer hasn't set dodgeInsetEdges, lets set it to BOTTOM so that
+                // we dodge any Snackbars
+                lp.dodgeInsetEdges = Gravity.BOTTOM;
+            }
         }
 
         @Override
         public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child,
                 View dependency) {
-            if (dependency instanceof Snackbar.SnackbarLayout) {
-                updateFabTranslationForSnackbar(parent, child, true);
-            } else if (dependency instanceof AppBarLayout) {
+            if (dependency instanceof AppBarLayout) {
                 // If we're depending on an AppBarLayout we will show/hide it automatically
                 // if the FAB is anchored to the AppBarLayout
                 updateFabVisibilityForAppBarLayout(parent, (AppBarLayout) dependency, child);
@@ -597,14 +592,6 @@
             return false;
         }
 
-        @Override
-        public void onDependentViewRemoved(CoordinatorLayout parent, FloatingActionButton child,
-                View dependency) {
-            if (dependency instanceof Snackbar.SnackbarLayout) {
-                updateFabTranslationForSnackbar(parent, child, true);
-            }
-        }
-
         private static boolean isBottomSheet(View view) {
             CoordinatorLayout.LayoutParams lp =
                     (CoordinatorLayout.LayoutParams) view.getLayoutParams();
@@ -677,63 +664,6 @@
             return true;
         }
 
-        private void updateFabTranslationForSnackbar(CoordinatorLayout parent,
-                final FloatingActionButton fab, boolean animationAllowed) {
-            final float targetTransY = getFabTranslationYForSnackbar(parent, fab);
-            if (mFabTranslationY == targetTransY) {
-                // We're already at (or currently animating to) the target value, return...
-                return;
-            }
-
-            final float currentTransY = ViewCompat.getTranslationY(fab);
-
-            // Make sure that any current animation is cancelled
-            if (mFabTranslationYAnimator != null && mFabTranslationYAnimator.isRunning()) {
-                mFabTranslationYAnimator.cancel();
-            }
-
-            if (animationAllowed && fab.isShown()
-                    && Math.abs(currentTransY - targetTransY) > (fab.getHeight() * 0.667f)) {
-                // If the FAB will be travelling by more than 2/3 of its height, let's animate
-                // it instead
-                if (mFabTranslationYAnimator == null) {
-                    mFabTranslationYAnimator = ViewUtils.createAnimator();
-                    mFabTranslationYAnimator.setInterpolator(
-                            AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
-                    mFabTranslationYAnimator.addUpdateListener(
-                            new ValueAnimatorCompat.AnimatorUpdateListener() {
-                                @Override
-                                public void onAnimationUpdate(ValueAnimatorCompat animator) {
-                                    ViewCompat.setTranslationY(fab,
-                                            animator.getAnimatedFloatValue());
-                                }
-                            });
-                }
-                mFabTranslationYAnimator.setFloatValues(currentTransY, targetTransY);
-                mFabTranslationYAnimator.start();
-            } else {
-                // Now update the translation Y
-                ViewCompat.setTranslationY(fab, targetTransY);
-            }
-
-            mFabTranslationY = targetTransY;
-        }
-
-        private float getFabTranslationYForSnackbar(CoordinatorLayout parent,
-                FloatingActionButton fab) {
-            float minOffset = 0;
-            final List<View> dependencies = parent.getDependencies(fab);
-            for (int i = 0, z = dependencies.size(); i < z; i++) {
-                final View view = dependencies.get(i);
-                if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(fab, view)) {
-                    minOffset = Math.min(minOffset,
-                            ViewCompat.getTranslationY(view) - view.getHeight());
-                }
-            }
-
-            return minOffset;
-        }
-
         @Override
         public boolean onLayoutChild(CoordinatorLayout parent, FloatingActionButton child,
                 int layoutDirection) {
@@ -756,8 +686,19 @@
             parent.onLayoutChild(child, layoutDirection);
             // Now offset it if needed
             offsetIfNeeded(parent, child);
-            // Make sure we translate the FAB for any displayed Snackbars (without an animation)
-            updateFabTranslationForSnackbar(parent, child, false);
+            return true;
+        }
+
+        @Override
+        public boolean getInsetDodgeRect(@NonNull CoordinatorLayout parent,
+                @NonNull FloatingActionButton child, @NonNull Rect rect) {
+            // Since we offset so that any internal shadow padding isn't shown, we need to make
+            // sure that the shadow isn't used for any dodge inset calculations
+            final Rect shadowPadding = child.mShadowPadding;
+            rect.set(child.getLeft() + shadowPadding.left,
+                    child.getTop() + shadowPadding.top,
+                    child.getRight() - shadowPadding.right,
+                    child.getBottom() - shadowPadding.bottom);
             return true;
         }
 
diff --git a/design/src/android/support/design/widget/Snackbar.java b/design/src/android/support/design/widget/Snackbar.java
index 2c1de6c..47c5a49 100644
--- a/design/src/android/support/design/widget/Snackbar.java
+++ b/design/src/android/support/design/widget/Snackbar.java
@@ -35,6 +35,7 @@
 import android.support.v4.view.WindowInsetsCompat;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -446,6 +447,7 @@
 
             if (lp instanceof CoordinatorLayout.LayoutParams) {
                 // If our LayoutParams are from a CoordinatorLayout, we'll setup our Behavior
+                final CoordinatorLayout.LayoutParams clp = (CoordinatorLayout.LayoutParams) lp;
 
                 final Behavior behavior = new Behavior();
                 behavior.setStartAlphaSwipeDistance(0.1f);
@@ -473,7 +475,9 @@
                         }
                     }
                 });
-                ((CoordinatorLayout.LayoutParams) lp).setBehavior(behavior);
+                clp.setBehavior(behavior);
+                // Also set the inset edge so that views can dodge the snackbar correctly
+                clp.insetEdge = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
             }
 
             mTargetParent.addView(mView);
@@ -628,7 +632,15 @@
         if (mCallback != null) {
             mCallback.onDismissed(this, event);
         }
-        // Lastly, remove the view from the parent (if attached)
+        if (Build.VERSION.SDK_INT < 11) {
+            // We need to hide the Snackbar on pre-v11 since it uses an old style Animation.
+            // ViewGroup has special handling in removeView() when getAnimation() != null in
+            // that it waits. This then means that the calculated insets are wrong and the
+            // any dodging views do not return. We workaround it by setting the view to gone while
+            // ViewGroup actually gets around to removing it.
+            mView.setVisibility(View.GONE);
+        }
+        // Lastly, hide and remove the view from the parent (if attached)
         final ViewParent parent = mView.getParent();
         if (parent instanceof ViewGroup) {
             ((ViewGroup) parent).removeView(mView);
diff --git a/design/tests/src/android/support/design/widget/SnackbarTestWithFAB.java b/design/tests/src/android/support/design/widget/SnackbarTestWithFAB.java
index a177cbb..894d126 100644
--- a/design/tests/src/android/support/design/widget/SnackbarTestWithFAB.java
+++ b/design/tests/src/android/support/design/widget/SnackbarTestWithFAB.java
@@ -20,7 +20,6 @@
 
 import android.support.design.test.R;
 import android.support.design.testutils.SnackbarUtils;
-import android.support.test.filters.SdkSuppress;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.view.View;
 
@@ -45,8 +44,7 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 11)
-    public void testShortSnackBarDodgesFab() {
+    public void testShortSnackbarDodgesFab() {
         final int[] originalFabPosition = new int[2];
         final View fab = mCoordinatorLayout.findViewById(R.id.fab);
         fab.getLocationOnScreen(originalFabPosition);
@@ -72,8 +70,7 @@
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 11)
-    public void testIndefiniteSnackBarDodgesFab() {
+    public void testIndefiniteSnackbarDodgesFab() {
         final int[] originalFabPosition = new int[2];
         final View fab = mCoordinatorLayout.findViewById(R.id.fab);
         fab.getLocationOnScreen(originalFabPosition);