CollapsingToolbarLayout.java revision a6a508b2296730ca6954aaebcca52a9962a5cb55
1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.support.design.widget; 18 19import android.content.Context; 20import android.content.res.TypedArray; 21import android.graphics.Canvas; 22import android.graphics.Color; 23import android.graphics.Rect; 24import android.support.annotation.ColorRes; 25import android.support.design.R; 26import android.support.v4.graphics.ColorUtils; 27import android.support.v4.view.ViewCompat; 28import android.support.v7.widget.Toolbar; 29import android.util.AttributeSet; 30import android.view.Gravity; 31import android.view.View; 32import android.view.ViewGroup; 33import android.view.animation.Animation; 34import android.view.animation.Transformation; 35import android.widget.FrameLayout; 36 37/** 38 * CollapsingToolbarLayout is a wrapper for {@link Toolbar} which implements a collapsing app bar. 39 * It is designed to be used as a direct child of a {@link AppBarLayout}. 40 * CollapsingToolbarLayout contains the following features: 41 * 42 * <h3>Collapsing title</h3> 43 * A title which is larger when the layout is fully visible but collapses and becomes smaller as 44 * the layout is scrolled off screen. You can set the title to display via 45 * {@link #setTitle(CharSequence)}. The title appearance can be tweaked via the 46 * {@code collapsedTextAppearance} and {@code expandedTextAppearance} attributes. 47 * 48 * <h3>Background scrim</h3> 49 * A full-bleed scrim which is show or hidden when the scroll position has hit a certain threshold. 50 * You can change the color via {@link #setForegroundScrimColor(int)}. 51 * 52 * <h3>Parallax scrolling children</h3> 53 * Child views can opt to be scrolled within this layout in a parallax fashion. 54 * See {@link LayoutParams#COLLAPSE_MODE_PARALLAX} and 55 * {@link LayoutParams#setParallaxMultiplier(float)}. 56 * 57 * <h3>Pinned position children</h3> 58 * Child views can opt to be pinned in space globally. This is useful when implementing a 59 * collapsing as it allows the {@link Toolbar} to be fixed in place even though this layout is 60 * moving. See {@link LayoutParams#COLLAPSE_MODE_PIN}. 61 * 62 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_collapsedTitleTextAppearance 63 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleTextAppearance 64 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_foregroundScrimColor 65 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMargin 66 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginStart 67 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginEnd 68 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom 69 */ 70public class CollapsingToolbarLayout extends FrameLayout implements AppBarLayout.AppBarLayoutChild { 71 72 private static final int SCRIM_ANIMATION_DURATION = 200; 73 74 private Toolbar mToolbar; 75 private View mDummyView; 76 77 private int mExpandedMarginLeft; 78 private int mExpandedMarginRight; 79 private int mExpandedMarginBottom; 80 81 private final Rect mRect = new Rect(); 82 private final CollapsingTextHelper mCollapsingTextHelper; 83 84 private int mForegroundScrimColor; 85 private int mCurrentForegroundColor; 86 private boolean mScrimIsShown; 87 88 public CollapsingToolbarLayout(Context context) { 89 this(context, null); 90 } 91 92 public CollapsingToolbarLayout(Context context, AttributeSet attrs) { 93 this(context, attrs, 0); 94 } 95 96 public CollapsingToolbarLayout(Context context, AttributeSet attrs, int defStyleAttr) { 97 super(context, attrs, defStyleAttr); 98 99 mCollapsingTextHelper = new CollapsingTextHelper(this); 100 mCollapsingTextHelper.setExpandedTextVerticalGravity(Gravity.BOTTOM); 101 mCollapsingTextHelper.setTextSizeInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR); 102 103 TypedArray a = context.obtainStyledAttributes(attrs, 104 R.styleable.CollapsingToolbarLayout, defStyleAttr, 105 R.style.Widget_Design_CollapsingToolbar); 106 107 mExpandedMarginLeft = mExpandedMarginRight = mExpandedMarginBottom = 108 a.getDimensionPixelSize(R.styleable.CollapsingToolbarLayout_expandedTitleMargin, 0); 109 110 final boolean isRtl = ViewCompat.getLayoutDirection(this) 111 == ViewCompat.LAYOUT_DIRECTION_RTL; 112 if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginStart)) { 113 final int marginStart = a.getDimensionPixelSize( 114 R.styleable.CollapsingToolbarLayout_expandedTitleMarginStart, 0); 115 if (isRtl) { 116 mExpandedMarginRight = marginStart; 117 } else { 118 mExpandedMarginLeft = marginStart; 119 } 120 } 121 if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginEnd)) { 122 final int marginEnd = a.getDimensionPixelSize( 123 R.styleable.CollapsingToolbarLayout_expandedTitleMarginEnd, 0); 124 if (isRtl) { 125 mExpandedMarginLeft = marginEnd; 126 } else { 127 mExpandedMarginRight = marginEnd; 128 } 129 } 130 if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginBottom)) { 131 mExpandedMarginBottom = a.getDimensionPixelSize( 132 R.styleable.CollapsingToolbarLayout_expandedTitleMarginBottom, 0); 133 } 134 135 int tp = a.getResourceId( 136 R.styleable.CollapsingToolbarLayout_expandedTitleTextAppearance, 137 R.style.TextAppearance_AppCompat_Title); 138 mCollapsingTextHelper.setExpandedTextAppearance(tp); 139 140 tp = a.getResourceId( 141 R.styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance, 142 R.style.TextAppearance_AppCompat_Widget_ActionBar_Title); 143 mCollapsingTextHelper.setCollapsedTextAppearance(tp); 144 145 mForegroundScrimColor = a.getColor( 146 R.styleable.CollapsingToolbarLayout_foregroundScrimColor, 0); 147 148 a.recycle(); 149 150 setWillNotDraw(false); 151 } 152 153 @Override 154 public void addView(View child, int index, ViewGroup.LayoutParams params) { 155 super.addView(child, index, params); 156 157 if (child instanceof Toolbar) { 158 mToolbar = (Toolbar) child; 159 mDummyView = new View(getContext()); 160 mToolbar.addView(mDummyView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 161 } 162 } 163 164 @Override 165 public void draw(Canvas canvas) { 166 super.draw(canvas); 167 168 // Let the collapsing text helper draw it's text 169 mCollapsingTextHelper.draw(canvas); 170 } 171 172 @Override 173 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 174 // This is a little weird. Our scrim needs to be behind the Toolbar (if it is present), 175 // but in front of any other children which are behind it. To do this we intercept the 176 // drawChild() call, and draw our scrim first when drawing the toolbar 177 if (child == mToolbar && Color.alpha(mCurrentForegroundColor) > 0) { 178 canvas.drawColor(mCurrentForegroundColor); 179 } 180 // Carry on drawing the child... 181 return super.drawChild(canvas, child, drawingTime); 182 } 183 184 @Override 185 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 186 super.onLayout(changed, left, top, right, bottom); 187 188 // Update our child view offset helpers 189 for (int i = 0, z = getChildCount(); i < z; i++) { 190 getViewOffsetHelper(getChildAt(i)).onViewLayout(); 191 } 192 193 // Now let the collapsing text helper update itself 194 mCollapsingTextHelper.onLayout(changed, left, top, right, bottom); 195 196 // Update the collapsed bounds by getting it's transformed bounds 197 ViewGroupUtils.getDescendantRect(this, mDummyView, mRect); 198 mCollapsingTextHelper.setCollapsedBounds(mRect.left, bottom - mRect.height(), 199 mRect.right, bottom); 200 // Update the expanded bounds 201 mCollapsingTextHelper.setExpandedBounds(left + mExpandedMarginLeft, mDummyView.getBottom(), 202 right - mExpandedMarginRight, bottom - mExpandedMarginBottom); 203 204 // Finally, set our minimum height to enable proper AppBarLayout collapsing 205 setMinimumHeight(mToolbar.getHeight()); 206 } 207 208 private static ViewOffsetHelper getViewOffsetHelper(View view) { 209 ViewOffsetHelper offsetHelper = (ViewOffsetHelper) view.getTag(R.id.view_offset_helper); 210 if (offsetHelper == null) { 211 offsetHelper = new ViewOffsetHelper(view); 212 view.setTag(R.id.view_offset_helper, offsetHelper); 213 } 214 return offsetHelper; 215 } 216 217 /** 218 * Set the title to display 219 * 220 * @param title 221 */ 222 public void setTitle(CharSequence title) { 223 mCollapsingTextHelper.setText(title); 224 } 225 226 /** 227 * @hide 228 */ 229 @Override 230 public void onOffsetUpdate(int leftRightOffset, int topBottomOffset) { 231 boolean toolbarOffsetChanged = false; 232 233 for (int i = 0, z = getChildCount(); i < z; i++) { 234 final View child = getChildAt(i); 235 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 236 final ViewOffsetHelper offsetHelper = getViewOffsetHelper(child); 237 boolean offsetChanged = false; 238 239 switch (lp.mCollapseMode) { 240 case LayoutParams.COLLAPSE_MODE_PIN: 241 if (getHeight() + topBottomOffset >= child.getHeight()) { 242 offsetChanged = offsetHelper.setTopAndBottomOffset(-topBottomOffset); 243 } 244 break; 245 case LayoutParams.COLLAPSE_MODE_PARALLAX: 246 offsetChanged = offsetHelper.setTopAndBottomOffset( 247 Math.round(-topBottomOffset * lp.mParallaxMult)); 248 break; 249 } 250 251 toolbarOffsetChanged = child == mToolbar && offsetChanged; 252 253 // Show or hide the scrim if needed 254 if (Color.alpha(mForegroundScrimColor) > 0) { 255 if (Math.abs(topBottomOffset) < getScrimTriggerOffset()) { 256 hideScrim(); 257 } else { 258 showScrim(); 259 } 260 } 261 } 262 263 // Update the collapsing text's fraction 264 mCollapsingTextHelper.setExpansionFraction(Math.abs(topBottomOffset) / 265 (float) (getHeight() - ViewCompat.getMinimumHeight(this))); 266 } 267 268 private void showScrim() { 269 if (mScrimIsShown) return; 270 Animation anim = new Animation() { 271 @Override 272 protected void applyTransformation(float interpolatedTime, Transformation t) { 273 final int originalAlpha = Color.alpha(mForegroundScrimColor); 274 mCurrentForegroundColor = ColorUtils.setAlphaComponent(mForegroundScrimColor, 275 AnimationUtils.lerp(0, originalAlpha, interpolatedTime)); 276 } 277 }; 278 anim.setDuration(SCRIM_ANIMATION_DURATION); 279 anim.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); 280 startAnimation(anim); 281 mScrimIsShown = true; 282 } 283 284 private void hideScrim() { 285 if (!mScrimIsShown) return; 286 287 Animation anim = new Animation() { 288 @Override 289 protected void applyTransformation(float interpolatedTime, Transformation t) { 290 final int originalAlpha = Color.alpha(mForegroundScrimColor); 291 mCurrentForegroundColor = ColorUtils.setAlphaComponent(mForegroundScrimColor, 292 AnimationUtils.lerp(originalAlpha, 0, interpolatedTime)); 293 } 294 }; 295 anim.setDuration(SCRIM_ANIMATION_DURATION); 296 anim.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); 297 startAnimation(anim); 298 299 mScrimIsShown = false; 300 } 301 302 /** 303 * Set the color to use for the foreground scrim. Providing a transparent color which disable 304 * the scrim. 305 * 306 * @param color ARGB color to use 307 * 308 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_foregroundScrimColor 309 * @see #getForegroundScrimColor() 310 */ 311 public void setForegroundScrimColor(int color) { 312 mForegroundScrimColor = color; 313 } 314 315 /** 316 * Set the color to use for the foreground scrim from resources. Providing a transparent color 317 * which disable the scrim. 318 * 319 * @param resId color resource id 320 * 321 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_foregroundScrimColor 322 * @see #getForegroundScrimColor() 323 */ 324 public void setForegroundScrimColorResource(@ColorRes int resId) { 325 mForegroundScrimColor = getResources().getColor(resId); 326 } 327 328 /** 329 * Returns the color which is used for the foreground scrim. 330 * 331 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_foregroundScrimColor 332 * @see #setForegroundScrimColor(int) 333 */ 334 public int getForegroundScrimColor() { 335 return mForegroundScrimColor; 336 } 337 338 /** 339 * Sets the text color and size for the collapsed title from the specified 340 * TextAppearance resource. 341 * 342 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_collapsedTitleTextAppearance 343 */ 344 public void setCollapsedTitleTextAppearance(int resId) { 345 mCollapsingTextHelper.setCollapsedTextAppearance(resId); 346 } 347 348 /** 349 * Sets the text color of the collapsed title. 350 * 351 * @param color The new text color in ARGB format 352 */ 353 public void setCollapsedTitleTextColor(int color) { 354 mCollapsingTextHelper.setCollapsedTextColor(color); 355 } 356 357 /** 358 * Sets the text color and size for the expanded title from the specified 359 * TextAppearance resource. 360 * 361 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleTextAppearance 362 */ 363 public void setExpandedTitleTextAppearance(int resId) { 364 mCollapsingTextHelper.setExpandedTextAppearance(resId); 365 } 366 367 /** 368 * Sets the text color of the expanded title. 369 * 370 * @param color The new text color in ARGB format 371 */ 372 public void setExpandedTitleColor(int color) { 373 mCollapsingTextHelper.setExpandedTextColor(color); 374 } 375 376 final int getScrimTriggerOffset() { 377 return 2 * ViewCompat.getMinimumHeight(this); 378 } 379 380 @Override 381 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 382 return p instanceof LayoutParams; 383 } 384 385 @Override 386 protected LayoutParams generateDefaultLayoutParams() { 387 return new LayoutParams(super.generateDefaultLayoutParams()); 388 } 389 390 @Override 391 public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) { 392 return new LayoutParams(getContext(), attrs); 393 } 394 395 @Override 396 protected FrameLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 397 return new LayoutParams(p); 398 } 399 400 public static class LayoutParams extends FrameLayout.LayoutParams { 401 402 private static final float DEFAULT_PARALLAX_MULTIPLIER = 0.5f; 403 404 /** 405 * The view will act as normal with no collapsing behavior. 406 */ 407 public static final int COLLAPSE_MODE_OFF = 0; 408 409 /** 410 * The view will pin in place until it reaches the bottom of the 411 * {@link CollapsingToolbarLayout}. 412 */ 413 public static final int COLLAPSE_MODE_PIN = 1; 414 415 /** 416 * The view will scroll in a parallax fashion. See {@link #setParallaxMultiplier(float)} 417 * to change the multiplier used. 418 */ 419 public static final int COLLAPSE_MODE_PARALLAX = 2; 420 421 int mCollapseMode = COLLAPSE_MODE_OFF; 422 float mParallaxMult = DEFAULT_PARALLAX_MULTIPLIER; 423 424 public LayoutParams(Context c, AttributeSet attrs) { 425 super(c, attrs); 426 427 TypedArray a = c.obtainStyledAttributes(attrs, 428 R.styleable.CollapsingAppBarLayout_LayoutParams); 429 mCollapseMode = a.getInt( 430 R.styleable.CollapsingAppBarLayout_LayoutParams_layout_collapseMode, 431 COLLAPSE_MODE_OFF); 432 setParallaxMultiplier(a.getFloat( 433 R.styleable.CollapsingAppBarLayout_LayoutParams_layout_collapseParallaxMultiplier, 434 DEFAULT_PARALLAX_MULTIPLIER)); 435 a.recycle(); 436 } 437 438 public LayoutParams(int width, int height) { 439 super(width, height); 440 } 441 442 public LayoutParams(int width, int height, int gravity) { 443 super(width, height, gravity); 444 } 445 446 public LayoutParams(ViewGroup.LayoutParams p) { 447 super(p); 448 } 449 450 public LayoutParams(MarginLayoutParams source) { 451 super(source); 452 } 453 454 public LayoutParams(FrameLayout.LayoutParams source) { 455 super(source); 456 } 457 458 /** 459 * Set the collapse mode. 460 * 461 * @param collapseMode one of {@link #COLLAPSE_MODE_OFF}, {@link #COLLAPSE_MODE_PIN} 462 * or {@link #COLLAPSE_MODE_PARALLAX}. 463 */ 464 public void setCollapseMode(int collapseMode) { 465 mCollapseMode = collapseMode; 466 } 467 468 /** 469 * Set the parallax scroll multiplier used in conjuction with 470 * {@link #COLLAPSE_MODE_PARALLAX}. A value of {@code 0.0} indicates no movement at all, 471 * {@code 1.0f} indicates normal scroll movement. 472 * 473 * @param multiplier the multiplier. 474 */ 475 public void setParallaxMultiplier(float multiplier) { 476 mParallaxMult = multiplier; 477 } 478 } 479} 480