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