CollapsingToolbarLayout.java revision 4837e2d38e11a8e85a2718e4620f73d32dcde184
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.Typeface; 24import android.graphics.drawable.ColorDrawable; 25import android.graphics.drawable.Drawable; 26import android.os.Build; 27import android.support.annotation.ColorInt; 28import android.support.annotation.DrawableRes; 29import android.support.annotation.IntDef; 30import android.support.annotation.NonNull; 31import android.support.annotation.Nullable; 32import android.support.annotation.StyleRes; 33import android.support.design.R; 34import android.support.v4.content.ContextCompat; 35import android.support.v4.view.GravityCompat; 36import android.support.v4.view.ViewCompat; 37import android.support.v4.view.WindowInsetsCompat; 38import android.support.v7.widget.Toolbar; 39import android.text.TextUtils; 40import android.util.AttributeSet; 41import android.view.Gravity; 42import android.view.View; 43import android.view.ViewGroup; 44import android.view.ViewParent; 45import android.widget.FrameLayout; 46 47import java.lang.annotation.Retention; 48import java.lang.annotation.RetentionPolicy; 49 50/** 51 * CollapsingToolbarLayout is a wrapper for {@link Toolbar} which implements a collapsing app bar. 52 * It is designed to be used as a direct child of a {@link AppBarLayout}. 53 * CollapsingToolbarLayout contains the following features: 54 * 55 * <h4>Collapsing title</h4> 56 * A title which is larger when the layout is fully visible but collapses and becomes smaller as 57 * the layout is scrolled off screen. You can set the title to display via 58 * {@link #setTitle(CharSequence)}. The title appearance can be tweaked via the 59 * {@code collapsedTextAppearance} and {@code expandedTextAppearance} attributes. 60 * 61 * <h4>Content scrim</h4> 62 * A full-bleed scrim which is show or hidden when the scroll position has hit a certain threshold. 63 * You can change this via {@link #setContentScrim(Drawable)}. 64 * 65 * <h4>Status bar scrim</h4> 66 * A scrim which is show or hidden behind the status bar when the scroll position has hit a certain 67 * threshold. You can change this via {@link #setStatusBarScrim(Drawable)}. This only works 68 * on {@link Build.VERSION_CODES#LOLLIPOP LOLLIPOP} devices when we set to fit system windows. 69 * 70 * <h4>Parallax scrolling children</h4> 71 * Child views can opt to be scrolled within this layout in a parallax fashion. 72 * See {@link LayoutParams#COLLAPSE_MODE_PARALLAX} and 73 * {@link LayoutParams#setParallaxMultiplier(float)}. 74 * 75 * <h4>Pinned position children</h4> 76 * Child views can opt to be pinned in space globally. This is useful when implementing a 77 * collapsing as it allows the {@link Toolbar} to be fixed in place even though this layout is 78 * moving. See {@link LayoutParams#COLLAPSE_MODE_PIN}. 79 * 80 * <p><strong>Do not manually add views to the Toolbar at run time</strong>. 81 * We will add a 'dummy view' to the Toolbar which allows us to work out the available space 82 * for the title. This can interfere with any views which you add.</p> 83 * 84 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_collapsedTitleTextAppearance 85 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleTextAppearance 86 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_contentScrim 87 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMargin 88 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginStart 89 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginEnd 90 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom 91 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_statusBarScrim 92 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_toolbarId 93 */ 94public class CollapsingToolbarLayout extends FrameLayout { 95 96 private static final int SCRIM_ANIMATION_DURATION = 600; 97 98 private boolean mRefreshToolbar = true; 99 private int mToolbarId; 100 private Toolbar mToolbar; 101 private View mToolbarDirectChild; 102 private View mDummyView; 103 104 private int mExpandedMarginStart; 105 private int mExpandedMarginTop; 106 private int mExpandedMarginEnd; 107 private int mExpandedMarginBottom; 108 109 private final Rect mTmpRect = new Rect(); 110 private final CollapsingTextHelper mCollapsingTextHelper; 111 private boolean mCollapsingTitleEnabled; 112 private boolean mDrawCollapsingTitle; 113 114 private Drawable mContentScrim; 115 private Drawable mStatusBarScrim; 116 private int mScrimAlpha; 117 private boolean mScrimsAreShown; 118 private ValueAnimatorCompat mScrimAnimator; 119 120 private AppBarLayout.OnOffsetChangedListener mOnOffsetChangedListener; 121 122 private int mCurrentOffset; 123 124 private WindowInsetsCompat mLastInsets; 125 126 public CollapsingToolbarLayout(Context context) { 127 this(context, null); 128 } 129 130 public CollapsingToolbarLayout(Context context, AttributeSet attrs) { 131 this(context, attrs, 0); 132 } 133 134 public CollapsingToolbarLayout(Context context, AttributeSet attrs, int defStyleAttr) { 135 super(context, attrs, defStyleAttr); 136 137 ThemeUtils.checkAppCompatTheme(context); 138 139 mCollapsingTextHelper = new CollapsingTextHelper(this); 140 mCollapsingTextHelper.setTextSizeInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR); 141 142 TypedArray a = context.obtainStyledAttributes(attrs, 143 R.styleable.CollapsingToolbarLayout, defStyleAttr, 144 R.style.Widget_Design_CollapsingToolbar); 145 146 mCollapsingTextHelper.setExpandedTextGravity( 147 a.getInt(R.styleable.CollapsingToolbarLayout_expandedTitleGravity, 148 GravityCompat.START | Gravity.BOTTOM)); 149 mCollapsingTextHelper.setCollapsedTextGravity( 150 a.getInt(R.styleable.CollapsingToolbarLayout_collapsedTitleGravity, 151 GravityCompat.START | Gravity.CENTER_VERTICAL)); 152 153 mExpandedMarginStart = mExpandedMarginTop = mExpandedMarginEnd = mExpandedMarginBottom = 154 a.getDimensionPixelSize(R.styleable.CollapsingToolbarLayout_expandedTitleMargin, 0); 155 156 if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginStart)) { 157 mExpandedMarginStart = a.getDimensionPixelSize( 158 R.styleable.CollapsingToolbarLayout_expandedTitleMarginStart, 0); 159 } 160 if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginEnd)) { 161 mExpandedMarginEnd = a.getDimensionPixelSize( 162 R.styleable.CollapsingToolbarLayout_expandedTitleMarginEnd, 0); 163 } 164 if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginTop)) { 165 mExpandedMarginTop = a.getDimensionPixelSize( 166 R.styleable.CollapsingToolbarLayout_expandedTitleMarginTop, 0); 167 } 168 if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleMarginBottom)) { 169 mExpandedMarginBottom = a.getDimensionPixelSize( 170 R.styleable.CollapsingToolbarLayout_expandedTitleMarginBottom, 0); 171 } 172 173 mCollapsingTitleEnabled = a.getBoolean( 174 R.styleable.CollapsingToolbarLayout_titleEnabled, true); 175 setTitle(a.getText(R.styleable.CollapsingToolbarLayout_title)); 176 177 // First load the default text appearances 178 mCollapsingTextHelper.setExpandedTextAppearance( 179 R.style.TextAppearance_Design_CollapsingToolbar_Expanded); 180 mCollapsingTextHelper.setCollapsedTextAppearance( 181 R.style.TextAppearance_AppCompat_Widget_ActionBar_Title); 182 183 // Now overlay any custom text appearances 184 if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleTextAppearance)) { 185 mCollapsingTextHelper.setExpandedTextAppearance( 186 a.getResourceId( 187 R.styleable.CollapsingToolbarLayout_expandedTitleTextAppearance, 0)); 188 } 189 if (a.hasValue(R.styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance)) { 190 mCollapsingTextHelper.setCollapsedTextAppearance( 191 a.getResourceId( 192 R.styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance, 0)); 193 } 194 195 setContentScrim(a.getDrawable(R.styleable.CollapsingToolbarLayout_contentScrim)); 196 setStatusBarScrim(a.getDrawable(R.styleable.CollapsingToolbarLayout_statusBarScrim)); 197 198 mToolbarId = a.getResourceId(R.styleable.CollapsingToolbarLayout_toolbarId, -1); 199 200 a.recycle(); 201 202 setWillNotDraw(false); 203 204 ViewCompat.setOnApplyWindowInsetsListener(this, 205 new android.support.v4.view.OnApplyWindowInsetsListener() { 206 @Override 207 public WindowInsetsCompat onApplyWindowInsets(View v, 208 WindowInsetsCompat insets) { 209 mLastInsets = insets; 210 requestLayout(); 211 return insets.consumeSystemWindowInsets(); 212 } 213 }); 214 } 215 216 @Override 217 protected void onAttachedToWindow() { 218 super.onAttachedToWindow(); 219 220 // Add an OnOffsetChangedListener if possible 221 final ViewParent parent = getParent(); 222 if (parent instanceof AppBarLayout) { 223 if (mOnOffsetChangedListener == null) { 224 mOnOffsetChangedListener = new OffsetUpdateListener(); 225 } 226 ((AppBarLayout) parent).addOnOffsetChangedListener(mOnOffsetChangedListener); 227 } 228 } 229 230 @Override 231 protected void onDetachedFromWindow() { 232 // Remove our OnOffsetChangedListener if possible and it exists 233 final ViewParent parent = getParent(); 234 if (mOnOffsetChangedListener != null && parent instanceof AppBarLayout) { 235 ((AppBarLayout) parent).removeOnOffsetChangedListener(mOnOffsetChangedListener); 236 } 237 238 super.onDetachedFromWindow(); 239 } 240 241 @Override 242 public void draw(Canvas canvas) { 243 super.draw(canvas); 244 245 // If we don't have a toolbar, the scrim will be not be drawn in drawChild() below. 246 // Instead, we draw it here, before our collapsing text. 247 ensureToolbar(); 248 if (mToolbar == null && mContentScrim != null && mScrimAlpha > 0) { 249 mContentScrim.mutate().setAlpha(mScrimAlpha); 250 mContentScrim.draw(canvas); 251 } 252 253 // Let the collapsing text helper draw its text 254 if (mCollapsingTitleEnabled && mDrawCollapsingTitle) { 255 mCollapsingTextHelper.draw(canvas); 256 } 257 258 // Now draw the status bar scrim 259 if (mStatusBarScrim != null && mScrimAlpha > 0) { 260 final int topInset = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0; 261 if (topInset > 0) { 262 mStatusBarScrim.setBounds(0, -mCurrentOffset, getWidth(), 263 topInset - mCurrentOffset); 264 mStatusBarScrim.mutate().setAlpha(mScrimAlpha); 265 mStatusBarScrim.draw(canvas); 266 } 267 } 268 } 269 270 @Override 271 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 272 // This is a little weird. Our scrim needs to be behind the Toolbar (if it is present), 273 // but in front of any other children which are behind it. To do this we intercept the 274 // drawChild() call, and draw our scrim first when drawing the toolbar 275 ensureToolbar(); 276 if (child == mToolbar && mContentScrim != null && mScrimAlpha > 0) { 277 mContentScrim.mutate().setAlpha(mScrimAlpha); 278 mContentScrim.draw(canvas); 279 } 280 281 // Carry on drawing the child... 282 return super.drawChild(canvas, child, drawingTime); 283 } 284 285 @Override 286 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 287 super.onSizeChanged(w, h, oldw, oldh); 288 if (mContentScrim != null) { 289 mContentScrim.setBounds(0, 0, w, h); 290 } 291 } 292 293 private void ensureToolbar() { 294 if (!mRefreshToolbar) { 295 return; 296 } 297 298 // First clear out the current Toolbar 299 mToolbar = null; 300 mToolbarDirectChild = null; 301 302 if (mToolbarId != -1) { 303 // If we have an ID set, try and find it and it's direct parent to us 304 mToolbar = (Toolbar) findViewById(mToolbarId); 305 if (mToolbar != null) { 306 mToolbarDirectChild = findDirectChild(mToolbar); 307 } 308 } 309 310 if (mToolbar == null) { 311 // If we don't have an ID, or couldn't find a Toolbar with the correct ID, try and find 312 // one from our direct children 313 Toolbar toolbar = null; 314 for (int i = 0, count = getChildCount(); i < count; i++) { 315 final View child = getChildAt(i); 316 if (child instanceof Toolbar) { 317 toolbar = (Toolbar) child; 318 break; 319 } 320 } 321 mToolbar = toolbar; 322 } 323 324 updateDummyView(); 325 mRefreshToolbar = false; 326 } 327 328 /** 329 * Returns the direct child of this layout, which itself is the ancestor of the 330 * given view. 331 */ 332 private View findDirectChild(final View descendant) { 333 View directChild = descendant; 334 for (ViewParent p = descendant.getParent(); p != this && p != null; p = p.getParent()) { 335 if (p instanceof View) { 336 directChild = (View) p; 337 } 338 } 339 return directChild; 340 } 341 342 private void updateDummyView() { 343 if (!mCollapsingTitleEnabled && mDummyView != null) { 344 // If we have a dummy view and we have our title disabled, remove it from its parent 345 final ViewParent parent = mDummyView.getParent(); 346 if (parent instanceof ViewGroup) { 347 ((ViewGroup) parent).removeView(mDummyView); 348 } 349 } 350 if (mCollapsingTitleEnabled && mToolbar != null) { 351 if (mDummyView == null) { 352 mDummyView = new View(getContext()); 353 } 354 if (mDummyView.getParent() == null) { 355 mToolbar.addView(mDummyView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 356 } 357 } 358 } 359 360 @Override 361 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 362 ensureToolbar(); 363 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 364 } 365 366 @Override 367 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 368 super.onLayout(changed, left, top, right, bottom); 369 370 // Update the collapsed bounds by getting it's transformed bounds. This needs to be done 371 // before the children are offset below 372 if (mCollapsingTitleEnabled && mDummyView != null) { 373 // We only draw the title if the dummy view is being displayed (Toolbar removes 374 // views if there is no space) 375 mDrawCollapsingTitle = mDummyView.isShown(); 376 377 if (mDrawCollapsingTitle) { 378 int bottomOffset = 0; 379 if (mToolbarDirectChild != null && mToolbarDirectChild != this) { 380 final LayoutParams lp = (LayoutParams) mToolbarDirectChild.getLayoutParams(); 381 bottomOffset = lp.bottomMargin; 382 } 383 ViewGroupUtils.getDescendantRect(this, mDummyView, mTmpRect); 384 mCollapsingTextHelper.setCollapsedBounds(mTmpRect.left, 385 bottom - mTmpRect.height() - bottomOffset, 386 mTmpRect.right, 387 bottom - bottomOffset); 388 389 final boolean isRtl = ViewCompat.getLayoutDirection(this) 390 == ViewCompat.LAYOUT_DIRECTION_RTL; 391 // Update the expanded bounds 392 mCollapsingTextHelper.setExpandedBounds( 393 isRtl ? mExpandedMarginEnd : mExpandedMarginStart, 394 mTmpRect.bottom + mExpandedMarginTop, 395 right - left - (isRtl ? mExpandedMarginStart : mExpandedMarginEnd), 396 bottom - top - mExpandedMarginBottom); 397 // Now recalculate using the new bounds 398 mCollapsingTextHelper.recalculate(); 399 } 400 } 401 402 // Update our child view offset helpers 403 for (int i = 0, z = getChildCount(); i < z; i++) { 404 final View child = getChildAt(i); 405 406 if (mLastInsets != null && !ViewCompat.getFitsSystemWindows(child)) { 407 final int insetTop = mLastInsets.getSystemWindowInsetTop(); 408 if (child.getTop() < insetTop) { 409 // If the child isn't set to fit system windows but is drawing within the inset 410 // offset it down 411 child.offsetTopAndBottom(insetTop); 412 } 413 } 414 415 getViewOffsetHelper(child).onViewLayout(); 416 } 417 418 // Finally, set our minimum height to enable proper AppBarLayout collapsing 419 if (mToolbar != null) { 420 if (mCollapsingTitleEnabled && TextUtils.isEmpty(mCollapsingTextHelper.getText())) { 421 // If we do not currently have a title, try and grab it from the Toolbar 422 mCollapsingTextHelper.setText(mToolbar.getTitle()); 423 } 424 if (mToolbarDirectChild == null || mToolbarDirectChild == this) { 425 setMinimumHeight(getHeightWithMargins(mToolbar)); 426 } else { 427 setMinimumHeight(getHeightWithMargins(mToolbarDirectChild)); 428 } 429 } 430 } 431 432 private static int getHeightWithMargins(@NonNull final View view) { 433 final ViewGroup.LayoutParams lp = view.getLayoutParams(); 434 if (lp instanceof MarginLayoutParams) { 435 final MarginLayoutParams mlp = (MarginLayoutParams) lp; 436 return view.getHeight() + mlp.topMargin + mlp.bottomMargin; 437 } 438 return view.getHeight(); 439 } 440 441 private static ViewOffsetHelper getViewOffsetHelper(View view) { 442 ViewOffsetHelper offsetHelper = (ViewOffsetHelper) view.getTag(R.id.view_offset_helper); 443 if (offsetHelper == null) { 444 offsetHelper = new ViewOffsetHelper(view); 445 view.setTag(R.id.view_offset_helper, offsetHelper); 446 } 447 return offsetHelper; 448 } 449 450 /** 451 * Sets the title to be displayed by this view, if enabled. 452 * 453 * @see #setTitleEnabled(boolean) 454 * @see #getTitle() 455 * 456 * @attr ref R.styleable#CollapsingToolbarLayout_title 457 */ 458 public void setTitle(@Nullable CharSequence title) { 459 mCollapsingTextHelper.setText(title); 460 } 461 462 /** 463 * Returns the title currently being displayed by this view. If the title is not enabled, then 464 * this will return {@code null}. 465 * 466 * @attr ref R.styleable#CollapsingToolbarLayout_title 467 */ 468 @Nullable 469 public CharSequence getTitle() { 470 return mCollapsingTitleEnabled ? mCollapsingTextHelper.getText() : null; 471 } 472 473 /** 474 * Sets whether this view should display its own title. 475 * 476 * <p>The title displayed by this view will shrink and grow based on the scroll offset.</p> 477 * 478 * @see #setTitle(CharSequence) 479 * @see #isTitleEnabled() 480 * 481 * @attr ref R.styleable#CollapsingToolbarLayout_titleEnabled 482 */ 483 public void setTitleEnabled(boolean enabled) { 484 if (enabled != mCollapsingTitleEnabled) { 485 mCollapsingTitleEnabled = enabled; 486 updateDummyView(); 487 requestLayout(); 488 } 489 } 490 491 /** 492 * Returns whether this view is currently displaying its own title. 493 * 494 * @see #setTitleEnabled(boolean) 495 * 496 * @attr ref R.styleable#CollapsingToolbarLayout_titleEnabled 497 */ 498 public boolean isTitleEnabled() { 499 return mCollapsingTitleEnabled; 500 } 501 502 /** 503 * Set whether the content scrim and/or status bar scrim should be shown or not. Any change 504 * in the vertical scroll may overwrite this value. Any visibility change will be animated if 505 * this view has already been laid out. 506 * 507 * @param shown whether the scrims should be shown 508 * 509 * @see #getStatusBarScrim() 510 * @see #getContentScrim() 511 */ 512 public void setScrimsShown(boolean shown) { 513 setScrimsShown(shown, ViewCompat.isLaidOut(this) && !isInEditMode()); 514 } 515 516 /** 517 * Set whether the content scrim and/or status bar scrim should be shown or not. Any change 518 * in the vertical scroll may overwrite this value. 519 * 520 * @param shown whether the scrims should be shown 521 * @param animate whether to animate the visibility change 522 * 523 * @see #getStatusBarScrim() 524 * @see #getContentScrim() 525 */ 526 public void setScrimsShown(boolean shown, boolean animate) { 527 if (mScrimsAreShown != shown) { 528 if (animate) { 529 animateScrim(shown ? 0xFF : 0x0); 530 } else { 531 setScrimAlpha(shown ? 0xFF : 0x0); 532 } 533 mScrimsAreShown = shown; 534 } 535 } 536 537 private void animateScrim(int targetAlpha) { 538 ensureToolbar(); 539 if (mScrimAnimator == null) { 540 mScrimAnimator = ViewUtils.createAnimator(); 541 mScrimAnimator.setDuration(SCRIM_ANIMATION_DURATION); 542 mScrimAnimator.setInterpolator( 543 targetAlpha > mScrimAlpha 544 ? AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR 545 : AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR); 546 mScrimAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() { 547 @Override 548 public void onAnimationUpdate(ValueAnimatorCompat animator) { 549 setScrimAlpha(animator.getAnimatedIntValue()); 550 } 551 }); 552 } else if (mScrimAnimator.isRunning()) { 553 mScrimAnimator.cancel(); 554 } 555 556 mScrimAnimator.setIntValues(mScrimAlpha, targetAlpha); 557 mScrimAnimator.start(); 558 } 559 560 private void setScrimAlpha(int alpha) { 561 if (alpha != mScrimAlpha) { 562 final Drawable contentScrim = mContentScrim; 563 if (contentScrim != null && mToolbar != null) { 564 ViewCompat.postInvalidateOnAnimation(mToolbar); 565 } 566 mScrimAlpha = alpha; 567 ViewCompat.postInvalidateOnAnimation(CollapsingToolbarLayout.this); 568 } 569 } 570 571 /** 572 * Set the drawable to use for the content scrim from resources. Providing null will disable 573 * the scrim functionality. 574 * 575 * @param drawable the drawable to display 576 * 577 * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim 578 * @see #getContentScrim() 579 */ 580 public void setContentScrim(@Nullable Drawable drawable) { 581 if (mContentScrim != drawable) { 582 if (mContentScrim != null) { 583 mContentScrim.setCallback(null); 584 } 585 if (drawable != null) { 586 mContentScrim = drawable.mutate(); 587 drawable.setBounds(0, 0, getWidth(), getHeight()); 588 drawable.setCallback(this); 589 drawable.setAlpha(mScrimAlpha); 590 } else { 591 mContentScrim = null; 592 } 593 ViewCompat.postInvalidateOnAnimation(this); 594 } 595 } 596 597 /** 598 * Set the color to use for the content scrim. 599 * 600 * @param color the color to display 601 * 602 * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim 603 * @see #getContentScrim() 604 */ 605 public void setContentScrimColor(@ColorInt int color) { 606 setContentScrim(new ColorDrawable(color)); 607 } 608 609 /** 610 * Set the drawable to use for the content scrim from resources. 611 * 612 * @param resId drawable resource id 613 * 614 * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim 615 * @see #getContentScrim() 616 */ 617 public void setContentScrimResource(@DrawableRes int resId) { 618 setContentScrim(ContextCompat.getDrawable(getContext(), resId)); 619 620 } 621 622 /** 623 * Returns the drawable which is used for the foreground scrim. 624 * 625 * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim 626 * @see #setContentScrim(Drawable) 627 */ 628 public Drawable getContentScrim() { 629 return mContentScrim; 630 } 631 632 /** 633 * Set the drawable to use for the status bar scrim from resources. 634 * Providing null will disable the scrim functionality. 635 * 636 * <p>This scrim is only shown when we have been given a top system inset.</p> 637 * 638 * @param drawable the drawable to display 639 * 640 * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim 641 * @see #getStatusBarScrim() 642 */ 643 public void setStatusBarScrim(@Nullable Drawable drawable) { 644 if (mStatusBarScrim != drawable) { 645 if (mStatusBarScrim != null) { 646 mStatusBarScrim.setCallback(null); 647 } 648 649 mStatusBarScrim = drawable; 650 drawable.setCallback(this); 651 drawable.mutate().setAlpha(mScrimAlpha); 652 ViewCompat.postInvalidateOnAnimation(this); 653 } 654 } 655 656 /** 657 * Set the color to use for the status bar scrim. 658 * 659 * <p>This scrim is only shown when we have been given a top system inset.</p> 660 * 661 * @param color the color to display 662 * 663 * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim 664 * @see #getStatusBarScrim() 665 */ 666 public void setStatusBarScrimColor(@ColorInt int color) { 667 setStatusBarScrim(new ColorDrawable(color)); 668 } 669 670 /** 671 * Set the drawable to use for the content scrim from resources. 672 * 673 * @param resId drawable resource id 674 * 675 * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim 676 * @see #getStatusBarScrim() 677 */ 678 public void setStatusBarScrimResource(@DrawableRes int resId) { 679 setStatusBarScrim(ContextCompat.getDrawable(getContext(), resId)); 680 } 681 682 /** 683 * Returns the drawable which is used for the status bar scrim. 684 * 685 * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim 686 * @see #setStatusBarScrim(Drawable) 687 */ 688 public Drawable getStatusBarScrim() { 689 return mStatusBarScrim; 690 } 691 692 /** 693 * Sets the text color and size for the collapsed title from the specified 694 * TextAppearance resource. 695 * 696 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_collapsedTitleTextAppearance 697 */ 698 public void setCollapsedTitleTextAppearance(@StyleRes int resId) { 699 mCollapsingTextHelper.setCollapsedTextAppearance(resId); 700 } 701 702 /** 703 * Sets the text color of the collapsed title. 704 * 705 * @param color The new text color in ARGB format 706 */ 707 public void setCollapsedTitleTextColor(@ColorInt int color) { 708 mCollapsingTextHelper.setCollapsedTextColor(color); 709 } 710 711 /** 712 * Sets the horizontal alignment of the collapsed title and the vertical gravity that will 713 * be used when there is extra space in the collapsed bounds beyond what is required for 714 * the title itself. 715 * 716 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_collapsedTitleGravity 717 */ 718 public void setCollapsedTitleGravity(int gravity) { 719 mCollapsingTextHelper.setCollapsedTextGravity(gravity); 720 } 721 722 /** 723 * Returns the horizontal and vertical alignment for title when collapsed. 724 * 725 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_collapsedTitleGravity 726 */ 727 public int getCollapsedTitleGravity() { 728 return mCollapsingTextHelper.getCollapsedTextGravity(); 729 } 730 731 /** 732 * Sets the text color and size for the expanded title from the specified 733 * TextAppearance resource. 734 * 735 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleTextAppearance 736 */ 737 public void setExpandedTitleTextAppearance(@StyleRes int resId) { 738 mCollapsingTextHelper.setExpandedTextAppearance(resId); 739 } 740 741 /** 742 * Sets the text color of the expanded title. 743 * 744 * @param color The new text color in ARGB format 745 */ 746 public void setExpandedTitleColor(@ColorInt int color) { 747 mCollapsingTextHelper.setExpandedTextColor(color); 748 } 749 750 /** 751 * Sets the horizontal alignment of the expanded title and the vertical gravity that will 752 * be used when there is extra space in the expanded bounds beyond what is required for 753 * the title itself. 754 * 755 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleGravity 756 */ 757 public void setExpandedTitleGravity(int gravity) { 758 mCollapsingTextHelper.setExpandedTextGravity(gravity); 759 } 760 761 /** 762 * Returns the horizontal and vertical alignment for title when expanded. 763 * 764 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleGravity 765 */ 766 public int getExpandedTitleGravity() { 767 return mCollapsingTextHelper.getExpandedTextGravity(); 768 } 769 770 /** 771 * Set the typeface to use for the collapsed title. 772 * 773 * @param typeface typeface to use, or {@code null} to use the default. 774 */ 775 public void setCollapsedTitleTypeface(@Nullable Typeface typeface) { 776 mCollapsingTextHelper.setCollapsedTypeface(typeface); 777 } 778 779 /** 780 * Returns the typeface used for the collapsed title. 781 */ 782 @NonNull 783 public Typeface getCollapsedTitleTypeface() { 784 return mCollapsingTextHelper.getCollapsedTypeface(); 785 } 786 787 /** 788 * Set the typeface to use for the expanded title. 789 * 790 * @param typeface typeface to use, or {@code null} to use the default. 791 */ 792 public void setExpandedTitleTypeface(@Nullable Typeface typeface) { 793 mCollapsingTextHelper.setExpandedTypeface(typeface); 794 } 795 796 /** 797 * Returns the typeface used for the expanded title. 798 */ 799 @NonNull 800 public Typeface getExpandedTitleTypeface() { 801 return mCollapsingTextHelper.getExpandedTypeface(); 802 } 803 804 /** 805 * Sets the expanded title margins. 806 * 807 * @param start the starting title margin in pixels 808 * @param top the top title margin in pixels 809 * @param end the ending title margin in pixels 810 * @param bottom the bottom title margin in pixels 811 * 812 * @see #getExpandedTitleMarginStart() 813 * @see #getExpandedTitleMarginTop() 814 * @see #getExpandedTitleMarginEnd() 815 * @see #getExpandedTitleMarginBottom() 816 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMargin 817 */ 818 public void setExpandedTitleMargin(int start, int top, int end, int bottom) { 819 mExpandedMarginStart = start; 820 mExpandedMarginTop = top; 821 mExpandedMarginEnd = end; 822 mExpandedMarginBottom = bottom; 823 requestLayout(); 824 } 825 826 /** 827 * @return the starting expanded title margin in pixels 828 * 829 * @see #setExpandedTitleMarginStart(int) 830 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginStart 831 */ 832 public int getExpandedTitleMarginStart() { 833 return mExpandedMarginStart; 834 } 835 836 /** 837 * Sets the starting expanded title margin in pixels. 838 * 839 * @param margin the starting title margin in pixels 840 * @see #getExpandedTitleMarginStart() 841 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginStart 842 */ 843 public void setExpandedTitleMarginStart(int margin) { 844 mExpandedMarginStart = margin; 845 requestLayout(); 846 } 847 848 /** 849 * @return the top expanded title margin in pixels 850 * @see #setExpandedTitleMarginTop(int) 851 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginTop 852 */ 853 public int getExpandedTitleMarginTop() { 854 return mExpandedMarginTop; 855 } 856 857 /** 858 * Sets the top expanded title margin in pixels. 859 * 860 * @param margin the top title margin in pixels 861 * @see #getExpandedTitleMarginTop() 862 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginTop 863 */ 864 public void setExpandedTitleMarginTop(int margin) { 865 mExpandedMarginTop = margin; 866 requestLayout(); 867 } 868 869 /** 870 * @return the ending expanded title margin in pixels 871 * @see #setExpandedTitleMarginEnd(int) 872 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginEnd 873 */ 874 public int getExpandedTitleMarginEnd() { 875 return mExpandedMarginEnd; 876 } 877 878 /** 879 * Sets the ending expanded title margin in pixels. 880 * 881 * @param margin the ending title margin in pixels 882 * @see #getExpandedTitleMarginEnd() 883 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginEnd 884 */ 885 public void setExpandedTitleMarginEnd(int margin) { 886 mExpandedMarginEnd = margin; 887 requestLayout(); 888 } 889 890 /** 891 * @return the bottom expanded title margin in pixels 892 * @see #setExpandedTitleMarginBottom(int) 893 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom 894 */ 895 public int getExpandedTitleMarginBottom() { 896 return mExpandedMarginBottom; 897 } 898 899 /** 900 * Sets the bottom expanded title margin in pixels. 901 * 902 * @param margin the bottom title margin in pixels 903 * @see #getExpandedTitleMarginBottom() 904 * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom 905 */ 906 public void setExpandedTitleMarginBottom(int margin) { 907 mExpandedMarginBottom = margin; 908 requestLayout(); 909 } 910 911 /** 912 * The additional offset used to define when to trigger the scrim visibility change. 913 */ 914 final int getScrimTriggerOffset() { 915 return 2 * ViewCompat.getMinimumHeight(this); 916 } 917 918 @Override 919 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 920 return p instanceof LayoutParams; 921 } 922 923 @Override 924 protected LayoutParams generateDefaultLayoutParams() { 925 return new LayoutParams(super.generateDefaultLayoutParams()); 926 } 927 928 @Override 929 public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) { 930 return new LayoutParams(getContext(), attrs); 931 } 932 933 @Override 934 protected FrameLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 935 return new LayoutParams(p); 936 } 937 938 public static class LayoutParams extends FrameLayout.LayoutParams { 939 940 private static final float DEFAULT_PARALLAX_MULTIPLIER = 0.5f; 941 942 /** @hide */ 943 @IntDef({ 944 COLLAPSE_MODE_OFF, 945 COLLAPSE_MODE_PIN, 946 COLLAPSE_MODE_PARALLAX 947 }) 948 @Retention(RetentionPolicy.SOURCE) 949 @interface CollapseMode {} 950 951 /** 952 * The view will act as normal with no collapsing behavior. 953 */ 954 public static final int COLLAPSE_MODE_OFF = 0; 955 956 /** 957 * The view will pin in place until it reaches the bottom of the 958 * {@link CollapsingToolbarLayout}. 959 */ 960 public static final int COLLAPSE_MODE_PIN = 1; 961 962 /** 963 * The view will scroll in a parallax fashion. See {@link #setParallaxMultiplier(float)} 964 * to change the multiplier used. 965 */ 966 public static final int COLLAPSE_MODE_PARALLAX = 2; 967 968 int mCollapseMode = COLLAPSE_MODE_OFF; 969 float mParallaxMult = DEFAULT_PARALLAX_MULTIPLIER; 970 971 public LayoutParams(Context c, AttributeSet attrs) { 972 super(c, attrs); 973 974 TypedArray a = c.obtainStyledAttributes(attrs, 975 R.styleable.CollapsingAppBarLayout_LayoutParams); 976 mCollapseMode = a.getInt( 977 R.styleable.CollapsingAppBarLayout_LayoutParams_layout_collapseMode, 978 COLLAPSE_MODE_OFF); 979 setParallaxMultiplier(a.getFloat( 980 R.styleable.CollapsingAppBarLayout_LayoutParams_layout_collapseParallaxMultiplier, 981 DEFAULT_PARALLAX_MULTIPLIER)); 982 a.recycle(); 983 } 984 985 public LayoutParams(int width, int height) { 986 super(width, height); 987 } 988 989 public LayoutParams(int width, int height, int gravity) { 990 super(width, height, gravity); 991 } 992 993 public LayoutParams(ViewGroup.LayoutParams p) { 994 super(p); 995 } 996 997 public LayoutParams(MarginLayoutParams source) { 998 super(source); 999 } 1000 1001 public LayoutParams(FrameLayout.LayoutParams source) { 1002 super(source); 1003 } 1004 1005 /** 1006 * Set the collapse mode. 1007 * 1008 * @param collapseMode one of {@link #COLLAPSE_MODE_OFF}, {@link #COLLAPSE_MODE_PIN} 1009 * or {@link #COLLAPSE_MODE_PARALLAX}. 1010 */ 1011 public void setCollapseMode(@CollapseMode int collapseMode) { 1012 mCollapseMode = collapseMode; 1013 } 1014 1015 /** 1016 * Returns the requested collapse mode. 1017 * 1018 * @return the current mode. One of {@link #COLLAPSE_MODE_OFF}, {@link #COLLAPSE_MODE_PIN} 1019 * or {@link #COLLAPSE_MODE_PARALLAX}. 1020 */ 1021 @CollapseMode 1022 public int getCollapseMode() { 1023 return mCollapseMode; 1024 } 1025 1026 /** 1027 * Set the parallax scroll multiplier used in conjunction with 1028 * {@link #COLLAPSE_MODE_PARALLAX}. A value of {@code 0.0} indicates no movement at all, 1029 * {@code 1.0f} indicates normal scroll movement. 1030 * 1031 * @param multiplier the multiplier. 1032 * 1033 * @see #getParallaxMultiplier() 1034 */ 1035 public void setParallaxMultiplier(float multiplier) { 1036 mParallaxMult = multiplier; 1037 } 1038 1039 /** 1040 * Returns the parallax scroll multiplier used in conjunction with 1041 * {@link #COLLAPSE_MODE_PARALLAX}. 1042 * 1043 * @see #setParallaxMultiplier(float) 1044 */ 1045 public float getParallaxMultiplier() { 1046 return mParallaxMult; 1047 } 1048 } 1049 1050 private class OffsetUpdateListener implements AppBarLayout.OnOffsetChangedListener { 1051 @Override 1052 public void onOffsetChanged(AppBarLayout layout, int verticalOffset) { 1053 mCurrentOffset = verticalOffset; 1054 1055 final int insetTop = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0; 1056 final int scrollRange = layout.getTotalScrollRange(); 1057 1058 for (int i = 0, z = getChildCount(); i < z; i++) { 1059 final View child = getChildAt(i); 1060 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1061 final ViewOffsetHelper offsetHelper = getViewOffsetHelper(child); 1062 1063 switch (lp.mCollapseMode) { 1064 case LayoutParams.COLLAPSE_MODE_PIN: 1065 if (getHeight() - insetTop + verticalOffset >= child.getHeight()) { 1066 offsetHelper.setTopAndBottomOffset(-verticalOffset); 1067 } 1068 break; 1069 case LayoutParams.COLLAPSE_MODE_PARALLAX: 1070 offsetHelper.setTopAndBottomOffset( 1071 Math.round(-verticalOffset * lp.mParallaxMult)); 1072 break; 1073 } 1074 } 1075 1076 // Show or hide the scrims if needed 1077 if (mContentScrim != null || mStatusBarScrim != null) { 1078 setScrimsShown(getHeight() + verticalOffset < getScrimTriggerOffset() + insetTop); 1079 } 1080 1081 if (mStatusBarScrim != null && insetTop > 0) { 1082 ViewCompat.postInvalidateOnAnimation(CollapsingToolbarLayout.this); 1083 } 1084 1085 // Update the collapsing text's fraction 1086 final int expandRange = getHeight() - ViewCompat.getMinimumHeight( 1087 CollapsingToolbarLayout.this) - insetTop; 1088 mCollapsingTextHelper.setExpansionFraction( 1089 Math.abs(verticalOffset) / (float) expandRange); 1090 1091 if (Math.abs(verticalOffset) == scrollRange) { 1092 // If we have some pinned children, and we're offset to only show those views, 1093 // we want to be elevate 1094 ViewCompat.setElevation(layout, layout.getTargetElevation()); 1095 } else { 1096 // Otherwise, we're inline with the content 1097 ViewCompat.setElevation(layout, 0f); 1098 } 1099 } 1100 } 1101} 1102