am 61c2d94f: Merge "AppBarLayout improvements" into lmp-mr1-ub-dev

* commit '61c2d94faeb79a7a96df402f201a226ed740694a':
  AppBarLayout improvements
diff --git a/design/api/current.txt b/design/api/current.txt
index 87fb679..8dd4fd8 100644
--- a/design/api/current.txt
+++ b/design/api/current.txt
@@ -6,15 +6,19 @@
   }
 
   public static abstract interface AppBarLayout.AppBarLayoutChild {
-    method public abstract void onOffsetUpdate(int, int);
+    method public abstract int onOffsetUpdate(int);
+    field public static final int STATE_ELEVATED_ABOVE = 1; // 0x1
+    field public static final int STATE_ELEVATED_INLINE = 0; // 0x0
   }
 
   public static class AppBarLayout.Behavior extends android.support.design.widget.ViewOffsetBehavior {
     ctor public AppBarLayout.Behavior();
     ctor public AppBarLayout.Behavior(android.content.Context, android.util.AttributeSet);
+    method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, int);
     method public void onNestedPreScroll(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View, int, int, int[]);
     method public void onNestedScroll(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View, int, int, int, int);
     method public boolean onStartNestedScroll(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View, android.view.View, int);
+    method public void onStopNestedScroll(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View);
   }
 
   public static class AppBarLayout.LayoutParams extends android.widget.LinearLayout.LayoutParams {
@@ -50,7 +54,7 @@
     ctor public CollapsingToolbarLayout(android.content.Context, android.util.AttributeSet);
     ctor public CollapsingToolbarLayout(android.content.Context, android.util.AttributeSet, int);
     method public int getForegroundScrimColor();
-    method public void onOffsetUpdate(int, int);
+    method public int onOffsetUpdate(int);
     method public void setCollapsedTitleTextAppearance(int);
     method public void setCollapsedTitleTextColor(int);
     method public void setExpandedTitleColor(int);
@@ -67,6 +71,8 @@
     ctor public CollapsingToolbarLayout.LayoutParams(android.view.ViewGroup.LayoutParams);
     ctor public CollapsingToolbarLayout.LayoutParams(android.view.ViewGroup.MarginLayoutParams);
     ctor public CollapsingToolbarLayout.LayoutParams(android.widget.FrameLayout.LayoutParams);
+    method public int getCollapseMode();
+    method public float getParallaxMultiplier();
     method public void setCollapseMode(int);
     method public void setParallaxMultiplier(float);
     field public static final int COLLAPSE_MODE_OFF = 0; // 0x0
diff --git a/design/lollipop/android/support/design/widget/ViewUtilsLollipop.java b/design/lollipop/android/support/design/widget/ViewUtilsLollipop.java
new file mode 100644
index 0000000..f080592
--- /dev/null
+++ b/design/lollipop/android/support/design/widget/ViewUtilsLollipop.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.view.View;
+import android.view.ViewOutlineProvider;
+
+class ViewUtilsLollipop {
+
+    static void setBoundsViewOutlineProvider(View view) {
+        view.setOutlineProvider(ViewOutlineProvider.BOUNDS);
+    }
+
+}
diff --git a/design/res/values/attrs.xml b/design/res/values/attrs.xml
index c018923..87ed281 100644
--- a/design/res/values/attrs.xml
+++ b/design/res/values/attrs.xml
@@ -160,6 +160,10 @@
         <attr name="maxActionInlineWidth" format="dimension"/>
     </declare-styleable>
 
+    <declare-styleable name="AppBarLayout">
+        <attr name="elevation" />
+    </declare-styleable>
+
     <declare-styleable name="AppBarLayout_LayoutParams">
         <attr name="layout_scrollFlags">
             <!-- The view will be scroll in direct relation to scroll events. This flag needs to be
diff --git a/design/res/values/dimens.xml b/design/res/values/dimens.xml
index f3a9111..3c8f77e 100644
--- a/design/res/values/dimens.xml
+++ b/design/res/values/dimens.xml
@@ -50,4 +50,6 @@
 
     <dimen name="snackbar_text_size">14sp</dimen>
 
+    <dimen name="appbar_elevation">4dp</dimen>
+
 </resources>
diff --git a/design/res/values/styles.xml b/design/res/values/styles.xml
index 572e278..f77c78e 100644
--- a/design/res/values/styles.xml
+++ b/design/res/values/styles.xml
@@ -100,5 +100,9 @@
         <item name="foregroundScrimColor">@android:color/transparent</item>
     </style>
 
+    <style name="Widget.Design.AppBarLayout" parent="android:Widget">
+        <item name="elevation">@dimen/appbar_elevation</item>
+    </style>
+
 </resources>
 
diff --git a/design/src/android/support/design/widget/AppBarLayout.java b/design/src/android/support/design/widget/AppBarLayout.java
index dc291ef..bf55293 100644
--- a/design/src/android/support/design/widget/AppBarLayout.java
+++ b/design/src/android/support/design/widget/AppBarLayout.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.support.annotation.IntDef;
 import android.support.design.R;
 import android.support.v4.view.ViewCompat;
 import android.util.AttributeSet;
@@ -26,7 +27,8 @@
 import android.view.animation.Interpolator;
 import android.widget.LinearLayout;
 
-import java.lang.ref.WeakReference;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 
 /**
@@ -91,15 +93,44 @@
      * receive offset updates, and provide extra information.
      */
     public interface AppBarLayoutChild {
+
+        /** @hide */
+        @IntDef({
+                STATE_ELEVATED_ABOVE,
+                STATE_ELEVATED_INLINE
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        @interface ElevatedState {}
+
+        /**
+         * The {@link AppBarLayout} should be elevated above any scrolling content, and this cast
+         * a shadow.
+         *
+         * @see #onOffsetUpdate(int)
+         */
+        int STATE_ELEVATED_ABOVE = 1;
+
+        /**
+         * The {@link AppBarLayout} should not be elevated above any scrolling content.
+         *
+         * @see #onOffsetUpdate(int)
+         */
+        int STATE_ELEVATED_INLINE = 0;
+
         /**
          * Called when the {@link AppBarLayout}'s layout offset has been changed. This allows
          * child views to implement custom behavior based on the offset (for instance pinning a
          * view at a certain y value).
          *
-         * @param leftRightOffset the left and right offset, in px
-         * @param topBottomOffset the top and bottom offset, in px
+         * <p>You can influence the elevation of the {@link AppBarLayout} by returning one of
+         * {@link #STATE_ELEVATED_INLINE} or {@link #STATE_ELEVATED_ABOVE}.
+         *
+         * @param verticalOffset the vertical offset for the parent {@link AppBarLayout}, in px
+         *
+         * @return one of {@link #STATE_ELEVATED_INLINE} or {@link #STATE_ELEVATED_ABOVE}.
          */
-        void onOffsetUpdate(int leftRightOffset, int topBottomOffset);
+        @ElevatedState
+        int onOffsetUpdate(int verticalOffset);
     }
 
     private static final int INVALID_SCROLL_RANGE = -1;
@@ -110,6 +141,8 @@
 
     boolean mHaveChildWithInterpolator;
 
+    private float mTargetElevation;
+
     public AppBarLayout(Context context) {
         this(context, null);
     }
@@ -117,6 +150,14 @@
     public AppBarLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
         setOrientation(VERTICAL);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppBarLayout,
+                0, R.style.Widget_Design_AppBarLayout);
+        mTargetElevation = a.getDimensionPixelSize(R.styleable.AppBarLayout_elevation, 0);
+        a.recycle();
+
+        // Use the bounds view outline provider so that we cast a shadow, even without a background
+        ViewUtils.setBoundsViewOutlineProvider(this);
     }
 
     @Override
@@ -225,15 +266,7 @@
      * Return the scroll range when scrolling up from a nested pre-scroll.
      */
     final int getUpNestedPreScrollRange() {
-        return getTotalScrollRange() - getUpNestedScrollRange();
-    }
-
-    /**
-     * Return the scroll range when scrolling up from a nested scroll.
-     * @return
-     */
-    final int getUpNestedScrollRange() {
-        return 0;
+        return getTotalScrollRange();
     }
 
     /**
@@ -272,20 +305,6 @@
         return mDownPreScrollRange = range;
     }
 
-    final int getMinimumHeightForVisibleOverlappingContent() {
-        final int minHeight = ViewCompat.getMinimumHeight(this);
-        if (minHeight != 0) {
-            // If this layout has a min height, use it (doubled)
-            return minHeight * 2;
-        }
-
-        // Otherwise, we'll use twice the min height of our last child
-        final int childCount = getChildCount();
-        return childCount >= 1
-                ? ViewCompat.getMinimumHeight(getChildAt(childCount - 1)) * 2
-                : 0;
-    }
-
     /**
      * Return the scroll range when scrolling down from a nested scroll.
      */
@@ -324,7 +343,39 @@
         return mDownScrollRange = range;
     }
 
+    final int getMinimumHeightForVisibleOverlappingContent() {
+        final int minHeight = ViewCompat.getMinimumHeight(this);
+        if (minHeight != 0) {
+            // If this layout has a min height, use it (doubled)
+            return minHeight * 2;
+        }
+
+        // Otherwise, we'll use twice the min height of our last child
+        final int childCount = getChildCount();
+        return childCount >= 1
+                ? ViewCompat.getMinimumHeight(getChildAt(childCount - 1)) * 2
+                : 0;
+    }
+
+    /**
+     * The elevation value to use when {@link AppBarLayout} is elevated above content.
+     */
+    final float getTargetElevation() {
+        return mTargetElevation;
+    }
+
     public static class LayoutParams extends LinearLayout.LayoutParams {
+
+        /** @hide */
+        @IntDef(flag=true, value={
+                SCROLL_FLAG_SCROLL,
+                SCROLL_FLAG_EXIT_UNTIL_COLLAPSED,
+                SCROLL_FLAG_ENTER_ALWAYS,
+                SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface ScrollFlags {}
+
         /**
          * The view will be scroll in direct relation to scroll events. This flag needs to be
          * set for any of the other flags to take effect. If any sibling views
@@ -417,7 +468,7 @@
          *
          * @attr ref android.support.design.R.styleable.AppBarLayout_LayoutParams_layout_scrollFlags
          */
-        public void setScrollFlags(int flags) {
+        public void setScrollFlags(@ScrollFlags int flags) {
             mScrollFlags = flags;
         }
 
@@ -428,6 +479,7 @@
          *
          * @attr ref android.support.design.R.styleable.AppBarLayout_LayoutParams_layout_scrollFlags
          */
+        @ScrollFlags
         public int getScrollFlags() {
             return mScrollFlags;
         }
@@ -464,6 +516,8 @@
     public static class Behavior extends ViewOffsetBehavior<AppBarLayout> {
         private int mSiblingOffsetTop;
 
+        private boolean mSkipNestedPreScroll;
+
         public Behavior() {}
 
         public Behavior(Context context, AttributeSet attrs) {
@@ -481,10 +535,18 @@
         @Override
         public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
                 View target, int dx, int dy, int[] consumed) {
-            if (dy != 0) {
-                consumed[1] = scroll(coordinatorLayout, child, dy, dy < 0
-                        ? -child.getDownNestedPreScrollRange()
-                        : -child.getUpNestedPreScrollRange());
+            if (dy != 0 && !mSkipNestedPreScroll) {
+                int min, max;
+                if (dy < 0) {
+                    // We're scrolling down
+                    min = -child.getTotalScrollRange();
+                    max = min + child.getDownNestedPreScrollRange();
+                } else {
+                    // We're scrolling up
+                    min = -child.getUpNestedPreScrollRange();
+                    max = 0;
+                }
+                consumed[1] = scroll(coordinatorLayout, child, dy, min, max);
             }
         }
 
@@ -492,36 +554,51 @@
         public void onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
                 View target, int dxConsumed, int dyConsumed,
                 int dxUnconsumed, int dyUnconsumed) {
-            int dy = 0;
-            if (dyConsumed > 0) {
-                // If the scrolling view is scrolling up, we offset
-                dy = dyConsumed;
-            } else if (dyUnconsumed < 0) {
-                // If the scrolling view is scrolling down, we offset anything it doesn't consume
-                dy = dyUnconsumed;
+            if (dyUnconsumed < 0) {
+                // If the scrolling view is scrolling down but not consuming, it's probably be at
+                // the top of it's content
+                scroll(coordinatorLayout, child, dyUnconsumed,
+                        -child.getDownNestedScrollRange(), 0);
+                // Set the expanding flag so that onNestedPreScroll doesn't handle any events
+                mSkipNestedPreScroll = true;
+            } else {
+                // As we're no longer handling nested scrolls, reset the skip flag
+                mSkipNestedPreScroll = false;
             }
+        }
 
-            if (dy != 0) {
-                scroll(coordinatorLayout, child, dy, dy < 0
-                        ? -child.getDownNestedScrollRange()
-                        : -child.getUpNestedScrollRange());
-            }
+        @Override
+        public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
+                View target) {
+            // Reset the skip flag
+            mSkipNestedPreScroll = false;
+        }
+
+        @Override
+        public boolean onLayoutChild(CoordinatorLayout parent, AppBarLayout child,
+                int layoutDirection) {
+            boolean handled = super.onLayoutChild(parent, child, layoutDirection);
+
+            // Make sure we update the elevation
+            final int elevationState = dispatchOffsetUpdates(child);
+            checkElevation(child, getTopAndBottomOffset(), elevationState);
+
+            return handled;
         }
 
         private int scroll(CoordinatorLayout coordinatorLayout, AppBarLayout appBarLayout,
-                int dy, int minOffset) {
+                int dy, int minOffset, int maxOffset) {
             return setAppBarTopBottomOffset(coordinatorLayout, appBarLayout,
-                    mSiblingOffsetTop - dy,
-                    minOffset);
+                    mSiblingOffsetTop - dy, minOffset, maxOffset);
         }
 
         private int setAppBarTopBottomOffset(CoordinatorLayout coordinatorLayout,
-                AppBarLayout appBarLayout, int newOffset, final int minOffset) {
+                AppBarLayout appBarLayout, int newOffset, int minOffset, int maxOffset) {
             final int curOffset = mSiblingOffsetTop;
             int consumed = 0;
 
-            if (curOffset >= minOffset) {
-                newOffset = MathUtils.constrain(newOffset, minOffset, 0);
+            if (minOffset != 0) {
+                newOffset = MathUtils.constrain(newOffset, minOffset, maxOffset);
 
                 if (curOffset != newOffset) {
                     boolean offsetChanged = setTopAndBottomOffset(
@@ -541,60 +618,78 @@
                         coordinatorLayout.dispatchDependentViewsChanged(appBarLayout);
                     }
 
-                    dispatchOffsetUpdates(appBarLayout);
+                    // Dispatch the updates to any AppBarLayoutChild children
+                    final int childState = dispatchOffsetUpdates(appBarLayout);
+                    checkElevation(appBarLayout, newOffset, childState);
                 }
             }
 
             return consumed;
         }
 
-        private void dispatchOffsetUpdates(AppBarLayout layout) {
-            for (int i = 0, z = layout.getChildCount(); i < z; i++) {
-                View child = layout.getChildAt(i);
-                if (child instanceof AppBarLayoutChild) {
-                    ((AppBarLayoutChild) child).onOffsetUpdate(
-                            getLeftAndRightOffset(), getTopAndBottomOffset());
+        private void checkElevation(AppBarLayout appBarLayout, int offset, int childState) {
+            if (appBarLayout.getHeight() + offset == 0) {
+                // If we're not visible, clear out the elevation
+                ViewCompat.setElevation(appBarLayout, 0f);
+            } else {
+                if (childState == AppBarLayoutChild.STATE_ELEVATED_ABOVE) {
+                    ViewCompat.setElevation(appBarLayout, appBarLayout.getTargetElevation());
+                } else {
+                    ViewCompat.setElevation(appBarLayout, 0f);
                 }
             }
         }
 
+        private int dispatchOffsetUpdates(AppBarLayout layout) {
+            for (int i = 0, z = layout.getChildCount(); i < z; i++) {
+                View child = layout.getChildAt(i);
+                if (child instanceof AppBarLayoutChild) {
+                    final int childState = ((AppBarLayoutChild) child)
+                            .onOffsetUpdate(getTopAndBottomOffset());
+
+                    if (childState == AppBarLayoutChild.STATE_ELEVATED_INLINE) {
+                        return childState;
+                    }
+                }
+            }
+
+            return AppBarLayoutChild.STATE_ELEVATED_ABOVE;
+        }
+
         private int interpolateOffset(AppBarLayout layout, final int offset) {
-            int heightSoFar = 0;
             final int absOffset = Math.abs(offset);
 
             for (int i = 0, z = layout.getChildCount(); i < z; i++) {
                 final View child = layout.getChildAt(i);
                 final AppBarLayout.LayoutParams childLp = (LayoutParams) child.getLayoutParams();
-                final int scrollFlags = childLp.getScrollFlags();
-                final int previousHeightSoFar = heightSoFar;
-                final int childHeight = child.getHeight();
+                final Interpolator interpolator = childLp.getScrollInterpolator();
 
-                int childScrollableHeight = 0;
-                if ((scrollFlags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) {
-                    // We're set to scroll so add the child's height
-                    childScrollableHeight += child.getHeight();
-                    if ((scrollFlags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) {
-                        // For a collapsing scroll, we to take the collapsed height into account.
-                        childScrollableHeight -= ViewCompat.getMinimumHeight(child);
-                    }
-                }
-
-                // Now update the height encountered
-                heightSoFar += childHeight;
-
-                if (absOffset >= previousHeightSoFar && absOffset <= heightSoFar) {
-                    final Interpolator interpolator = childLp.getScrollInterpolator();
-
+                if (absOffset >= child.getTop() && absOffset <= child.getBottom()) {
                     if (interpolator != null) {
-                        final int offsetForView = absOffset - previousHeightSoFar;
-                        final int interpolatedDiff = Math.round(childScrollableHeight *
-                                interpolator.getInterpolation(
-                                        offsetForView / (float) childScrollableHeight));
+                        int childScrollableHeight = 0;
+                        final int flags = childLp.getScrollFlags();
+                        if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) {
+                            // We're set to scroll so add the child's height
+                            childScrollableHeight += child.getHeight();
+                            if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) {
+                                // For a collapsing scroll, we to take the collapsed height into account.
+                                childScrollableHeight -= ViewCompat.getMinimumHeight(child);
+                            }
+                        }
 
-                        return Integer.signum(offset) * (previousHeightSoFar + interpolatedDiff);
-                    } else {
-                        break;
+                        if (childScrollableHeight > 0) {
+                            final int offsetForView = absOffset - child.getTop();
+                            final int interpolatedDiff = Math.round(childScrollableHeight *
+                                    interpolator.getInterpolation(
+                                            offsetForView / (float) childScrollableHeight));
+
+                            return Integer.signum(offset) * (child.getTop() + interpolatedDiff);
+                        }
                     }
+
+                    // If we get to here then the view on the offset isn't suitable for interpolated
+                    // scrolling. So break out of the loop
+                    break;
                 }
             }
 
diff --git a/design/src/android/support/design/widget/CollapsingToolbarLayout.java b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
index 418a2ad..ab36ed5 100644
--- a/design/src/android/support/design/widget/CollapsingToolbarLayout.java
+++ b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
@@ -22,6 +22,7 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.support.annotation.ColorRes;
+import android.support.annotation.IntDef;
 import android.support.design.R;
 import android.support.v4.graphics.ColorUtils;
 import android.support.v4.view.ViewCompat;
@@ -34,6 +35,9 @@
 import android.view.animation.Transformation;
 import android.widget.FrameLayout;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * CollapsingToolbarLayout is a wrapper for {@link Toolbar} which implements a collapsing app bar.
  * It is designed to be used as a direct child of a {@link AppBarLayout}.
@@ -45,7 +49,7 @@
  * {@link #setTitle(CharSequence)}. The title appearance can be tweaked via the
  * {@code collapsedTextAppearance} and {@code expandedTextAppearance} attributes.
  *
- * <h3>Background scrim</h3>
+ * <h3>Foreground scrim</h3>
  * A full-bleed scrim which is show or hidden when the scroll position has hit a certain threshold.
  * You can change the color via {@link #setForegroundScrimColor(int)}.
  *
@@ -69,7 +73,7 @@
  */
 public class CollapsingToolbarLayout extends FrameLayout implements AppBarLayout.AppBarLayoutChild {
 
-    private static final int SCRIM_ANIMATION_DURATION = 200;
+    private static final int SCRIM_ANIMATION_DURATION = 600;
 
     private Toolbar mToolbar;
     private View mDummyView;
@@ -165,6 +169,12 @@
     public void draw(Canvas canvas) {
         super.draw(canvas);
 
+        // If we don't have a toolbar, the scrim will be not be drawn in drawChild() below.
+        // Instead, we draw it here, before our collapsing text.
+        if (mToolbar == null && Color.alpha(mCurrentForegroundColor) > 0) {
+            canvas.drawColor(mCurrentForegroundColor);
+        }
+
         // Let the collapsing text helper draw it's text
         mCollapsingTextHelper.draw(canvas);
     }
@@ -177,6 +187,7 @@
         if (child == mToolbar && Color.alpha(mCurrentForegroundColor) > 0) {
             canvas.drawColor(mCurrentForegroundColor);
         }
+
         // Carry on drawing the child...
         return super.drawChild(canvas, child, drawingTime);
     }
@@ -227,57 +238,73 @@
      * @hide
      */
     @Override
-    public void onOffsetUpdate(int leftRightOffset, int topBottomOffset) {
-        boolean toolbarOffsetChanged = false;
+    public int onOffsetUpdate(int verticalOffset) {
+        int pinnedHeight = 0;
 
         for (int i = 0, z = getChildCount(); i < z; i++) {
             final View child = getChildAt(i);
             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
             final ViewOffsetHelper offsetHelper = getViewOffsetHelper(child);
-            boolean offsetChanged = false;
 
             switch (lp.mCollapseMode) {
                 case LayoutParams.COLLAPSE_MODE_PIN:
-                    if (getHeight() + topBottomOffset >= child.getHeight()) {
-                        offsetChanged = offsetHelper.setTopAndBottomOffset(-topBottomOffset);
+                    if (getHeight() + verticalOffset >= child.getHeight()) {
+                        offsetHelper.setTopAndBottomOffset(-verticalOffset);
                     }
+                    pinnedHeight += child.getHeight();
                     break;
                 case LayoutParams.COLLAPSE_MODE_PARALLAX:
-                    offsetChanged = offsetHelper.setTopAndBottomOffset(
-                            Math.round(-topBottomOffset * lp.mParallaxMult));
+                    offsetHelper.setTopAndBottomOffset(
+                            Math.round(-verticalOffset * lp.mParallaxMult));
                     break;
             }
+        }
 
-            toolbarOffsetChanged = child == mToolbar && offsetChanged;
-
-            // Show or hide the scrim if needed
-            if (Color.alpha(mForegroundScrimColor) > 0) {
-                if (Math.abs(topBottomOffset) < getScrimTriggerOffset()) {
-                    hideScrim();
-                } else {
-                    showScrim();
-                }
+        // Show or hide the scrim if needed
+        if (Color.alpha(mForegroundScrimColor) > 0) {
+            if (getHeight() + verticalOffset < getScrimTriggerOffset()) {
+                showScrim();
+            } else {
+                hideScrim();
             }
         }
 
         // Update the collapsing text's fraction
-        mCollapsingTextHelper.setExpansionFraction(Math.abs(topBottomOffset) /
+        mCollapsingTextHelper.setExpansionFraction(Math.abs(verticalOffset) /
                 (float) (getHeight() - ViewCompat.getMinimumHeight(this)));
+
+        if (pinnedHeight > 0 && (getHeight() + verticalOffset) == pinnedHeight) {
+            // If we have some pinned children, and we're offset to only show those views,
+            // we want to be elevate
+            return STATE_ELEVATED_ABOVE;
+        } else {
+            // Otherwise, we're inline with the content
+            return STATE_ELEVATED_INLINE;
+        }
     }
 
     private void showScrim() {
         if (mScrimIsShown) return;
+
         Animation anim = new Animation() {
             @Override
             protected void applyTransformation(float interpolatedTime, Transformation t) {
                 final int originalAlpha = Color.alpha(mForegroundScrimColor);
                 mCurrentForegroundColor = ColorUtils.setAlphaComponent(mForegroundScrimColor,
                         AnimationUtils.lerp(0, originalAlpha, interpolatedTime));
+
+                // We need to manually invalidate ourselves and the Toolbar to ensure the scrim
+                // is drawn
+                invalidate();
+                if (mToolbar != null) {
+                    mToolbar.invalidate();
+                }
             }
         };
         anim.setDuration(SCRIM_ANIMATION_DURATION);
         anim.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
         startAnimation(anim);
+
         mScrimIsShown = true;
     }
 
@@ -290,6 +317,13 @@
                 final int originalAlpha = Color.alpha(mForegroundScrimColor);
                 mCurrentForegroundColor = ColorUtils.setAlphaComponent(mForegroundScrimColor,
                         AnimationUtils.lerp(originalAlpha, 0, interpolatedTime));
+
+                // We need to manually invalidate ourselves and the Toolbar to ensure the scrim
+                // is drawn
+                invalidate();
+                if (mToolbar != null) {
+                    mToolbar.invalidate();
+                }
             }
         };
         anim.setDuration(SCRIM_ANIMATION_DURATION);
@@ -401,6 +435,15 @@
 
         private static final float DEFAULT_PARALLAX_MULTIPLIER = 0.5f;
 
+        /** @hide */
+        @IntDef({
+                COLLAPSE_MODE_OFF,
+                COLLAPSE_MODE_PIN,
+                COLLAPSE_MODE_PARALLAX
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        @interface CollapseMode {}
+
         /**
          * The view will act as normal with no collapsing behavior.
          */
@@ -461,19 +504,42 @@
          * @param collapseMode one of {@link #COLLAPSE_MODE_OFF}, {@link #COLLAPSE_MODE_PIN}
          *                     or {@link #COLLAPSE_MODE_PARALLAX}.
          */
-        public void setCollapseMode(int collapseMode) {
+        public void setCollapseMode(@CollapseMode int collapseMode) {
             mCollapseMode = collapseMode;
         }
 
         /**
-         * Set the parallax scroll multiplier used in conjuction with
+         * Returns the requested collapse mode.
+         *
+         * @return the current mode. One of {@link #COLLAPSE_MODE_OFF}, {@link #COLLAPSE_MODE_PIN}
+         * or {@link #COLLAPSE_MODE_PARALLAX}.
+         */
+        @CollapseMode
+        public int getCollapseMode() {
+            return mCollapseMode;
+        }
+
+        /**
+         * Set the parallax scroll multiplier used in conjunction with
          * {@link #COLLAPSE_MODE_PARALLAX}. A value of {@code 0.0} indicates no movement at all,
          * {@code 1.0f} indicates normal scroll movement.
          *
          * @param multiplier the multiplier.
+         *
+         * @see #getParallaxMultiplier()
          */
         public void setParallaxMultiplier(float multiplier) {
             mParallaxMult = multiplier;
         }
+
+        /**
+         * Returns the parallax scroll multiplier used in conjunction with
+         * {@link #COLLAPSE_MODE_PARALLAX}.
+         *
+         * @see #setParallaxMultiplier(float)
+         */
+        public float getParallaxMultiplier() {
+            return mParallaxMult;
+        }
     }
 }
diff --git a/design/src/android/support/design/widget/ViewUtils.java b/design/src/android/support/design/widget/ViewUtils.java
new file mode 100644
index 0000000..34fdc68
--- /dev/null
+++ b/design/src/android/support/design/widget/ViewUtils.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.os.Build;
+import android.view.View;
+
+class ViewUtils {
+
+    private interface ViewUtilsImpl {
+        void setBoundsViewOutlineProvider(View view);
+    }
+
+    private static class ViewUtilsImplBase implements ViewUtilsImpl {
+        @Override
+        public void setBoundsViewOutlineProvider(View view) {
+            // no-op
+        }
+    }
+
+    private static class ViewUtilsImplLollipop implements ViewUtilsImpl {
+        @Override
+        public void setBoundsViewOutlineProvider(View view) {
+            ViewUtilsLollipop.setBoundsViewOutlineProvider(view);
+        }
+    }
+
+    private static final ViewUtilsImpl IMPL;
+
+    static {
+        final int version = Build.VERSION.SDK_INT;
+        if (version >= 21) {
+            IMPL = new ViewUtilsImplLollipop();
+        } else {
+            IMPL = new ViewUtilsImplBase();
+        }
+    }
+
+    static void setBoundsViewOutlineProvider(View view) {
+        IMPL.setBoundsViewOutlineProvider(view);
+    }
+
+}