CollapsingToolbarLayout.java revision 5c7d7bbceeaebc46dc2e4b2135cc330414f716d4
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.Rect; 23import android.graphics.drawable.ColorDrawable; 24import android.graphics.drawable.Drawable; 25import android.os.Build; 26import android.support.annotation.DrawableRes; 27import android.support.annotation.IntDef; 28import android.support.annotation.Nullable; 29import android.support.design.R; 30import android.support.v4.content.ContextCompat; 31import android.support.v4.view.GravityCompat; 32import android.support.v4.view.ViewCompat; 33import android.support.v4.view.WindowInsetsCompat; 34import android.support.v7.widget.Toolbar; 35import android.util.AttributeSet; 36import android.view.Gravity; 37import android.view.View; 38import android.view.ViewGroup; 39import android.view.ViewParent; 40import android.widget.FrameLayout; 41 42import java.lang.annotation.Retention; 43import java.lang.annotation.RetentionPolicy; 44 45/** 46 * CollapsingToolbarLayout is a wrapper for {@link Toolbar} which implements a collapsing app bar. 47 * It is designed to be used as a direct child of a {@link AppBarLayout}. 48 * CollapsingToolbarLayout contains the following features: 49 * 50 * <h3>Collapsing title</h3> 51 * A title which is larger when the layout is fully visible but collapses and becomes smaller as 52 * the layout is scrolled off screen. You can set the title to display via 53 * {@link #setTitle(CharSequence)}. The title appearance can be tweaked via the 54 * {@code collapsedTextAppearance} and {@code expandedTextAppearance} attributes. 55 * 56 * <h3>Content scrim</h3> 57 * A full-bleed scrim which is show or hidden when the scroll position has hit a certain threshold. 58 * You can change this via {@link #setContentScrim(Drawable)}. 59 * 60 * <h3>Status bar scrim</h3> 61 * A scrim which is show or hidden behind the status bar when the scroll position has hit a certain 62 * threshold. You can change this via {@link #setStatusBarScrim(Drawable)}. This only works 63 * on {@link Build.VERSION_CODES#LOLLIPOP LOLLIPOP} devices when we set to fit system windows. 64 * 65 * <h3>Parallax scrolling children</h3> 66 * Child views can opt to be scrolled within this layout in a parallax fashion. 67 * See {@link LayoutParams#COLLAPSE_MODE_PARALLAX} and 68 * {@link LayoutParams#setParallaxMultiplier(float)}. 69 * 70 * <h3>Pinned position children</h3> 71 * Child views can opt to be pinned in space globally. This is useful when implementing a 72 * collapsing as it allows the {@link Toolbar} to be fixed in place even though this layout is 73 * moving. See {@link LayoutParams#COLLAPSE_MODE_PIN}. 74 * 75 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_collapsedTitleTextAppearance 76 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleTextAppearance 77 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_contentScrim 78 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMargin 79 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginStart 80 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginEnd 81 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom 82 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_statusBarScrim 83 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_toolbarId 84 */ 85public class CollapsingToolbarLayout extends FrameLayout { 86 87 private static final int SCRIM_ANIMATION_DURATION = 600; 88 89 private boolean mRefreshToolbar = true; 90 private int mToolbarId; 91 private Toolbar mToolbar; 92 private View mDummyView; 93 94 private int mExpandedMarginLeft; 95 private int mExpandedMarginTop; 96 private int mExpandedMarginRight; 97 private int mExpandedMarginBottom; 98 99 private final Rect mTmpRect = new Rect(); 100 private final CollapsingTextHelper mCollapsingTextHelper; 101 102 private Drawable mContentScrim; 103 private Drawable mStatusBarScrim; 104 private int mScrimAlpha; 105 private boolean mScrimsAreShown; 106 private ValueAnimatorCompat mScrimAnimator; 107 108 private AppBarLayout.OnOffsetChangedListener mOnOffsetChangedListener; 109 110 private int mCurrentOffset; 111 112 private WindowInsetsCompat mLastInsets; 113 114 public CollapsingToolbarLayout(Context context) { 115 this(context, null); 116 } 117 118 public CollapsingToolbarLayout(Context context, AttributeSet attrs) { 119 this(context, attrs, 0); 120 } 121 122 public CollapsingToolbarLayout(Context context, AttributeSet attrs, int defStyleAttr) { 123 super(context, attrs, defStyleAttr); 124 125 mCollapsingTextHelper = new CollapsingTextHelper(this); 126 mCollapsingTextHelper.setTextSizeInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR); 127 128 TypedArray a = context.obtainStyledAttributes(attrs, 129 R.styleable.CollapsingToolbarLayout, defStyleAttr, 130 R.style.Widget_Design_CollapsingToolbar); 131 132 mCollapsingTextHelper.setExpandedTextGravity( 133 a.getInt(R.styleable.CollapsingToolbarLayout_expandedTitleGravity, 134 GravityCompat.START | Gravity.BOTTOM)); 135 mCollapsingTextHelper.setCollapsedTextGravity( 136 a.getInt(R.styleable.CollapsingToolbarLayout_collapsedTitleGravity, 137 GravityCompat.START | Gravity.CENTER_VERTICAL)); 138 139 mExpandedMarginLeft = mExpandedMarginTop = mExpandedMarginRight = mExpandedMarginBottom = 140 a.getDimensionPixelSize(R.styleable.CollapsingToolbarLayout_expandedTitleMargin, 0); 141 142 final boolean isRtl = ViewCompat.getLayoutDirection(this) 143 == ViewCompat.LAYOUT_DIRECTION_RTL; 144 if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginStart)) { 145 final int marginStart = a.getDimensionPixelSize( 146 R.styleable.CollapsingToolbarLayout_expandedTitleMarginStart, 0); 147 if (isRtl) { 148 mExpandedMarginRight = marginStart; 149 } else { 150 mExpandedMarginLeft = marginStart; 151 } 152 } 153 if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginEnd)) { 154 final int marginEnd = a.getDimensionPixelSize( 155 R.styleable.CollapsingToolbarLayout_expandedTitleMarginEnd, 0); 156 if (isRtl) { 157 mExpandedMarginLeft = marginEnd; 158 } else { 159 mExpandedMarginRight = marginEnd; 160 } 161 } 162 if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginTop)) { 163 mExpandedMarginTop = a.getDimensionPixelSize( 164 R.styleable.CollapsingToolbarLayout_expandedTitleMarginTop, 0); 165 } 166 if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginBottom)) { 167 mExpandedMarginBottom = a.getDimensionPixelSize( 168 R.styleable.CollapsingToolbarLayout_expandedTitleMarginBottom, 0); 169 } 170 171 int tp = a.getResourceId( 172 R.styleable.CollapsingToolbarLayout_expandedTitleTextAppearance, 173 R.style.TextAppearance_AppCompat_Title); 174 mCollapsingTextHelper.setExpandedTextAppearance(tp); 175 176 tp = a.getResourceId( 177 R.styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance, 178 R.style.TextAppearance_AppCompat_Widget_ActionBar_Title); 179 mCollapsingTextHelper.setCollapsedTextAppearance(tp); 180 181 setContentScrim(a.getDrawable(R.styleable.CollapsingToolbarLayout_contentScrim)); 182 setStatusBarScrim(a.getDrawable(R.styleable.CollapsingToolbarLayout_statusBarScrim)); 183 184 mToolbarId = a.getResourceId(R.styleable.CollapsingToolbarLayout_toolbarId, -1); 185 186 a.recycle(); 187 188 setWillNotDraw(false); 189 190 ViewCompat.setOnApplyWindowInsetsListener(this, 191 new android.support.v4.view.OnApplyWindowInsetsListener() { 192 @Override 193 public WindowInsetsCompat onApplyWindowInsets(View v, 194 WindowInsetsCompat insets) { 195 mLastInsets = insets; 196 requestLayout(); 197 return insets.consumeSystemWindowInsets(); 198 } 199 }); 200 } 201 202 @Override 203 protected void onAttachedToWindow() { 204 super.onAttachedToWindow(); 205 206 // Add an OnOffsetChangedListener if possible 207 final ViewParent parent = getParent(); 208 if (parent instanceof AppBarLayout) { 209 if (mOnOffsetChangedListener == null) { 210 mOnOffsetChangedListener = new OffsetUpdateListener(); 211 } 212 ((AppBarLayout) parent).addOnOffsetChangedListener(mOnOffsetChangedListener); 213 } 214 } 215 216 @Override 217 protected void onDetachedFromWindow() { 218 // Remove our OnOffsetChangedListener if possible and it exists 219 final ViewParent parent = getParent(); 220 if (mOnOffsetChangedListener != null && parent instanceof AppBarLayout) { 221 ((AppBarLayout) parent).removeOnOffsetChangedListener(mOnOffsetChangedListener); 222 } 223 224 super.onDetachedFromWindow(); 225 } 226 227 @Override 228 public void draw(Canvas canvas) { 229 super.draw(canvas); 230 231 // If we don't have a toolbar, the scrim will be not be drawn in drawChild() below. 232 // Instead, we draw it here, before our collapsing text. 233 ensureToolbar(); 234 if (mToolbar == null && mContentScrim != null && mScrimAlpha > 0) { 235 mContentScrim.mutate().setAlpha(mScrimAlpha); 236 mContentScrim.draw(canvas); 237 } 238 239 // Let the collapsing text helper draw it's text 240 mCollapsingTextHelper.draw(canvas); 241 242 // Now draw the status bar scrim 243 if (mStatusBarScrim != null && mScrimAlpha > 0) { 244 final int topInset = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0; 245 if (topInset > 0) { 246 mStatusBarScrim.setBounds(0, -mCurrentOffset, getWidth(), 247 topInset - mCurrentOffset); 248 mStatusBarScrim.mutate().setAlpha(mScrimAlpha); 249 mStatusBarScrim.draw(canvas); 250 } 251 } 252 } 253 254 @Override 255 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 256 // This is a little weird. Our scrim needs to be behind the Toolbar (if it is present), 257 // but in front of any other children which are behind it. To do this we intercept the 258 // drawChild() call, and draw our scrim first when drawing the toolbar 259 ensureToolbar(); 260 if (child == mToolbar && mContentScrim != null && mScrimAlpha > 0) { 261 mContentScrim.mutate().setAlpha(mScrimAlpha); 262 mContentScrim.draw(canvas); 263 } 264 265 // Carry on drawing the child... 266 return super.drawChild(canvas, child, drawingTime); 267 } 268 269 @Override 270 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 271 super.onSizeChanged(w, h, oldw, oldh); 272 if (mContentScrim != null) { 273 mContentScrim.setBounds(0, 0, w, h); 274 } 275 } 276 277 private void ensureToolbar() { 278 if (!mRefreshToolbar) { 279 return; 280 } 281 282 Toolbar fallback = null, selected = null; 283 284 for (int i = 0, count = getChildCount(); i < count; i++) { 285 final View child = getChildAt(i); 286 if (child instanceof Toolbar) { 287 if (mToolbarId != -1) { 288 // There's a toolbar id set so try and find it... 289 if (mToolbarId == child.getId()) { 290 // We found the primary Toolbar, use it 291 selected = (Toolbar) child; 292 break; 293 } 294 if (fallback == null) { 295 // We'll record the first Toolbar as our fallback 296 fallback = (Toolbar) child; 297 } 298 } else { 299 // We don't have a id to check for so just use the first we come across 300 selected = (Toolbar) child; 301 break; 302 } 303 } 304 } 305 306 if (selected == null) { 307 // If we didn't find a primary Toolbar, use the fallback 308 selected = fallback; 309 } 310 311 if (selected != null) { 312 mToolbar = selected; 313 mDummyView = new View(getContext()); 314 mToolbar.addView(mDummyView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 315 } else { 316 mToolbar = null; 317 mDummyView = null; 318 } 319 mRefreshToolbar = false; 320 } 321 322 @Override 323 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 324 ensureToolbar(); 325 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 326 } 327 328 @Override 329 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 330 super.onLayout(changed, left, top, right, bottom); 331 332 // Update our child view offset helpers 333 for (int i = 0, z = getChildCount(); i < z; i++) { 334 final View child = getChildAt(i); 335 336 if (mLastInsets != null && !ViewCompat.getFitsSystemWindows(child)) { 337 final int insetTop = mLastInsets.getSystemWindowInsetTop(); 338 if (child.getTop() < insetTop) { 339 // If the child isn't set to fit system windows but is drawing within the inset 340 // offset it down 341 child.offsetTopAndBottom(insetTop); 342 } 343 } 344 345 getViewOffsetHelper(child).onViewLayout(); 346 } 347 348 // Update the collapsed bounds by getting it's transformed bounds 349 if (mDummyView != null) { 350 ViewGroupUtils.getDescendantRect(this, mDummyView, mTmpRect); 351 mCollapsingTextHelper.setCollapsedBounds(mTmpRect.left, bottom - mTmpRect.height(), 352 mTmpRect.right, bottom); 353 // Update the expanded bounds 354 mCollapsingTextHelper.setExpandedBounds( 355 mExpandedMarginLeft, mTmpRect.bottom, 356 right - left - mExpandedMarginRight, 357 bottom - top - mExpandedMarginBottom); 358 359 mCollapsingTextHelper.recalculate(); 360 } 361 362 // Finally, set our minimum height to enable proper AppBarLayout collapsing 363 if (mToolbar != null) { 364 setMinimumHeight(mToolbar.getHeight()); 365 } 366 } 367 368 private static ViewOffsetHelper getViewOffsetHelper(View view) { 369 ViewOffsetHelper offsetHelper = (ViewOffsetHelper) view.getTag(R.id.view_offset_helper); 370 if (offsetHelper == null) { 371 offsetHelper = new ViewOffsetHelper(view); 372 view.setTag(R.id.view_offset_helper, offsetHelper); 373 } 374 return offsetHelper; 375 } 376 377 /** 378 * Set the title to display 379 * 380 * @param title 381 */ 382 public void setTitle(CharSequence title) { 383 mCollapsingTextHelper.setText(title); 384 } 385 386 private void showScrim() { 387 if (!mScrimsAreShown) { 388 if (ViewCompat.isLaidOut(this) && !isInEditMode()) { 389 animateScrim(255); 390 } else { 391 setScrimAlpha(255); 392 } 393 mScrimsAreShown = true; 394 } 395 } 396 397 private void hideScrim() { 398 if (mScrimsAreShown) { 399 if (ViewCompat.isLaidOut(this) && !isInEditMode()) { 400 animateScrim(0); 401 } else { 402 setScrimAlpha(0); 403 } 404 mScrimsAreShown = false; 405 } 406 } 407 408 private void animateScrim(int targetAlpha) { 409 ensureToolbar(); 410 if (mScrimAnimator == null) { 411 mScrimAnimator = ViewUtils.createAnimator(); 412 mScrimAnimator.setDuration(SCRIM_ANIMATION_DURATION); 413 mScrimAnimator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); 414 mScrimAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() { 415 @Override 416 public void onAnimationUpdate(ValueAnimatorCompat animator) { 417 setScrimAlpha(animator.getAnimatedIntValue()); 418 } 419 }); 420 } else if (mScrimAnimator.isRunning()) { 421 mScrimAnimator.cancel(); 422 } 423 424 mScrimAnimator.setIntValues(mScrimAlpha, targetAlpha); 425 mScrimAnimator.start(); 426 } 427 428 private void setScrimAlpha(int alpha) { 429 if (alpha != mScrimAlpha) { 430 final Drawable contentScrim = mContentScrim; 431 if (contentScrim != null && mToolbar != null) { 432 ViewCompat.postInvalidateOnAnimation(mToolbar); 433 } 434 mScrimAlpha = alpha; 435 ViewCompat.postInvalidateOnAnimation(CollapsingToolbarLayout.this); 436 } 437 } 438 439 /** 440 * Set the drawable to use for the content scrim from resources. Providing null will disable 441 * the scrim functionality. 442 * 443 * @param drawable the drawable to display 444 * 445 * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim 446 * @see #getContentScrim() 447 */ 448 public void setContentScrim(@Nullable Drawable drawable) { 449 if (mContentScrim != drawable) { 450 if (mContentScrim != null) { 451 mContentScrim.setCallback(null); 452 } 453 454 mContentScrim = drawable; 455 drawable.setBounds(0, 0, getWidth(), getHeight()); 456 drawable.setCallback(this); 457 drawable.mutate().setAlpha(mScrimAlpha); 458 ViewCompat.postInvalidateOnAnimation(this); 459 } 460 } 461 462 /** 463 * Set the color to use for the content scrim. 464 * 465 * @param color the color to display 466 * 467 * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim 468 * @see #getContentScrim() 469 */ 470 public void setContentScrimColor(int color) { 471 setContentScrim(new ColorDrawable(color)); 472 } 473 474 /** 475 * Set the drawable to use for the content scrim from resources. 476 * 477 * @param resId drawable resource id 478 * 479 * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim 480 * @see #getContentScrim() 481 */ 482 public void setContentScrimResource(@DrawableRes int resId) { 483 setContentScrim(ContextCompat.getDrawable(getContext(), resId)); 484 485 } 486 487 /** 488 * Returns the drawable which is used for the foreground scrim. 489 * 490 * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim 491 * @see #setContentScrim(Drawable) 492 */ 493 public Drawable getContentScrim() { 494 return mContentScrim; 495 } 496 497 /** 498 * Set the drawable to use for the status bar scrim from resources. 499 * Providing null will disable the scrim functionality. 500 * 501 * <p>This scrim is only shown when we have been given a top system inset.</p> 502 * 503 * @param drawable the drawable to display 504 * 505 * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim 506 * @see #getStatusBarScrim() 507 */ 508 public void setStatusBarScrim(@Nullable Drawable drawable) { 509 if (mStatusBarScrim != drawable) { 510 if (mStatusBarScrim != null) { 511 mStatusBarScrim.setCallback(null); 512 } 513 514 mStatusBarScrim = drawable; 515 drawable.setCallback(this); 516 drawable.mutate().setAlpha(mScrimAlpha); 517 ViewCompat.postInvalidateOnAnimation(this); 518 } 519 } 520 521 /** 522 * Set the color to use for the status bar scrim. 523 * 524 * <p>This scrim is only shown when we have been given a top system inset.</p> 525 * 526 * @param color the color to display 527 * 528 * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim 529 * @see #getStatusBarScrim() 530 */ 531 public void setStatusBarScrimColor(int color) { 532 setStatusBarScrim(new ColorDrawable(color)); 533 } 534 535 /** 536 * Set the drawable to use for the content scrim from resources. 537 * 538 * @param resId drawable resource id 539 * 540 * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim 541 * @see #getStatusBarScrim() 542 */ 543 public void setStatusBarScrimResource(@DrawableRes int resId) { 544 setStatusBarScrim(ContextCompat.getDrawable(getContext(), resId)); 545 } 546 547 /** 548 * Returns the drawable which is used for the status bar scrim. 549 * 550 * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim 551 * @see #setStatusBarScrim(Drawable) 552 */ 553 public Drawable getStatusBarScrim() { 554 return mStatusBarScrim; 555 } 556 557 /** 558 * Sets the text color and size for the collapsed title from the specified 559 * TextAppearance resource. 560 * 561 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_collapsedTitleTextAppearance 562 */ 563 public void setCollapsedTitleTextAppearance(int resId) { 564 mCollapsingTextHelper.setCollapsedTextAppearance(resId); 565 } 566 567 /** 568 * Sets the text color of the collapsed title. 569 * 570 * @param color The new text color in ARGB format 571 */ 572 public void setCollapsedTitleTextColor(int color) { 573 mCollapsingTextHelper.setCollapsedTextColor(color); 574 } 575 576 /** 577 * Sets the horizontal alignment of the collapsed title and the vertical gravity that will 578 * be used when there is extra space in the collapsed bounds beyond what is required for 579 * the title itself. 580 * 581 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_collapsedTitleGravity 582 */ 583 public void setCollapsedTitleGravity(int gravity) { 584 mCollapsingTextHelper.setExpandedTextGravity(gravity); 585 } 586 587 /** 588 * Returns the horizontal and vertical alignment for title when collapsed. 589 * 590 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_collapsedTitleGravity 591 */ 592 public int getCollapsedTitleGravity() { 593 return mCollapsingTextHelper.getCollapsedTextGravity(); 594 } 595 596 /** 597 * Sets the text color and size for the expanded title from the specified 598 * TextAppearance resource. 599 * 600 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleTextAppearance 601 */ 602 public void setExpandedTitleTextAppearance(int resId) { 603 mCollapsingTextHelper.setExpandedTextAppearance(resId); 604 } 605 606 /** 607 * Sets the text color of the expanded title. 608 * 609 * @param color The new text color in ARGB format 610 */ 611 public void setExpandedTitleColor(int color) { 612 mCollapsingTextHelper.setExpandedTextColor(color); 613 } 614 615 /** 616 * Sets the horizontal alignment of the expanded title and the vertical gravity that will 617 * be used when there is extra space in the expanded bounds beyond what is required for 618 * the title itself. 619 * 620 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleGravity 621 */ 622 public void setExpandedTitleGravity(int gravity) { 623 mCollapsingTextHelper.setExpandedTextGravity(gravity); 624 } 625 626 /** 627 * Returns the horizontal and vertical alignment for title when expanded. 628 * 629 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleGravity 630 */ 631 public int getExpandedTitleGravity() { 632 return mCollapsingTextHelper.getExpandedTextGravity(); 633 } 634 635 /** 636 * The additional offset used to define when to trigger the scrim visibility change. 637 */ 638 final int getScrimTriggerOffset() { 639 return 2 * ViewCompat.getMinimumHeight(this); 640 } 641 642 @Override 643 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 644 return p instanceof LayoutParams; 645 } 646 647 @Override 648 protected LayoutParams generateDefaultLayoutParams() { 649 return new LayoutParams(super.generateDefaultLayoutParams()); 650 } 651 652 @Override 653 public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) { 654 return new LayoutParams(getContext(), attrs); 655 } 656 657 @Override 658 protected FrameLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 659 return new LayoutParams(p); 660 } 661 662 public static class LayoutParams extends FrameLayout.LayoutParams { 663 664 private static final float DEFAULT_PARALLAX_MULTIPLIER = 0.5f; 665 666 /** @hide */ 667 @IntDef({ 668 COLLAPSE_MODE_OFF, 669 COLLAPSE_MODE_PIN, 670 COLLAPSE_MODE_PARALLAX 671 }) 672 @Retention(RetentionPolicy.SOURCE) 673 @interface CollapseMode {} 674 675 /** 676 * The view will act as normal with no collapsing behavior. 677 */ 678 public static final int COLLAPSE_MODE_OFF = 0; 679 680 /** 681 * The view will pin in place until it reaches the bottom of the 682 * {@link CollapsingToolbarLayout}. 683 */ 684 public static final int COLLAPSE_MODE_PIN = 1; 685 686 /** 687 * The view will scroll in a parallax fashion. See {@link #setParallaxMultiplier(float)} 688 * to change the multiplier used. 689 */ 690 public static final int COLLAPSE_MODE_PARALLAX = 2; 691 692 int mCollapseMode = COLLAPSE_MODE_OFF; 693 float mParallaxMult = DEFAULT_PARALLAX_MULTIPLIER; 694 695 public LayoutParams(Context c, AttributeSet attrs) { 696 super(c, attrs); 697 698 TypedArray a = c.obtainStyledAttributes(attrs, 699 R.styleable.CollapsingAppBarLayout_LayoutParams); 700 mCollapseMode = a.getInt( 701 R.styleable.CollapsingAppBarLayout_LayoutParams_layout_collapseMode, 702 COLLAPSE_MODE_OFF); 703 setParallaxMultiplier(a.getFloat( 704 R.styleable.CollapsingAppBarLayout_LayoutParams_layout_collapseParallaxMultiplier, 705 DEFAULT_PARALLAX_MULTIPLIER)); 706 a.recycle(); 707 } 708 709 public LayoutParams(int width, int height) { 710 super(width, height); 711 } 712 713 public LayoutParams(int width, int height, int gravity) { 714 super(width, height, gravity); 715 } 716 717 public LayoutParams(ViewGroup.LayoutParams p) { 718 super(p); 719 } 720 721 public LayoutParams(MarginLayoutParams source) { 722 super(source); 723 } 724 725 public LayoutParams(FrameLayout.LayoutParams source) { 726 super(source); 727 } 728 729 /** 730 * Set the collapse mode. 731 * 732 * @param collapseMode one of {@link #COLLAPSE_MODE_OFF}, {@link #COLLAPSE_MODE_PIN} 733 * or {@link #COLLAPSE_MODE_PARALLAX}. 734 */ 735 public void setCollapseMode(@CollapseMode int collapseMode) { 736 mCollapseMode = collapseMode; 737 } 738 739 /** 740 * Returns the requested collapse mode. 741 * 742 * @return the current mode. One of {@link #COLLAPSE_MODE_OFF}, {@link #COLLAPSE_MODE_PIN} 743 * or {@link #COLLAPSE_MODE_PARALLAX}. 744 */ 745 @CollapseMode 746 public int getCollapseMode() { 747 return mCollapseMode; 748 } 749 750 /** 751 * Set the parallax scroll multiplier used in conjunction with 752 * {@link #COLLAPSE_MODE_PARALLAX}. A value of {@code 0.0} indicates no movement at all, 753 * {@code 1.0f} indicates normal scroll movement. 754 * 755 * @param multiplier the multiplier. 756 * 757 * @see #getParallaxMultiplier() 758 */ 759 public void setParallaxMultiplier(float multiplier) { 760 mParallaxMult = multiplier; 761 } 762 763 /** 764 * Returns the parallax scroll multiplier used in conjunction with 765 * {@link #COLLAPSE_MODE_PARALLAX}. 766 * 767 * @see #setParallaxMultiplier(float) 768 */ 769 public float getParallaxMultiplier() { 770 return mParallaxMult; 771 } 772 } 773 774 private class OffsetUpdateListener implements AppBarLayout.OnOffsetChangedListener { 775 @Override 776 public void onOffsetChanged(AppBarLayout layout, int verticalOffset) { 777 mCurrentOffset = verticalOffset; 778 779 final int insetTop = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0; 780 final int scrollRange = layout.getTotalScrollRange(); 781 782 for (int i = 0, z = getChildCount(); i < z; i++) { 783 final View child = getChildAt(i); 784 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 785 final ViewOffsetHelper offsetHelper = getViewOffsetHelper(child); 786 787 switch (lp.mCollapseMode) { 788 case LayoutParams.COLLAPSE_MODE_PIN: 789 if (getHeight() - insetTop + verticalOffset >= child.getHeight()) { 790 offsetHelper.setTopAndBottomOffset(-verticalOffset); 791 } 792 break; 793 case LayoutParams.COLLAPSE_MODE_PARALLAX: 794 offsetHelper.setTopAndBottomOffset( 795 Math.round(-verticalOffset * lp.mParallaxMult)); 796 break; 797 } 798 } 799 800 // Show or hide the scrims if needed 801 if (mContentScrim != null || mStatusBarScrim != null) { 802 if (getHeight() + verticalOffset < getScrimTriggerOffset() + insetTop) { 803 showScrim(); 804 } else { 805 hideScrim(); 806 } 807 } 808 809 if (mStatusBarScrim != null && insetTop > 0) { 810 ViewCompat.postInvalidateOnAnimation(CollapsingToolbarLayout.this); 811 } 812 813 // Update the collapsing text's fraction 814 final int expandRange = getHeight() - ViewCompat.getMinimumHeight( 815 CollapsingToolbarLayout.this) - insetTop; 816 mCollapsingTextHelper.setExpansionFraction( 817 Math.abs(verticalOffset) / (float) expandRange); 818 819 if (Math.abs(verticalOffset) == scrollRange) { 820 // If we have some pinned children, and we're offset to only show those views, 821 // we want to be elevate 822 ViewCompat.setElevation(layout, layout.getTargetElevation()); 823 } else { 824 // Otherwise, we're inline with the content 825 ViewCompat.setElevation(layout, 0f); 826 } 827 } 828 } 829} 830