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