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