1 /* 2 * Copyright (C) 2012 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 17 package android.support.v7.internal.widget; 18 19 import android.content.Context; 20 import android.content.res.Configuration; 21 import android.content.res.TypedArray; 22 import android.graphics.Canvas; 23 import android.graphics.Rect; 24 import android.graphics.drawable.Drawable; 25 import android.os.Build; 26 import android.os.Parcelable; 27 import android.support.v4.view.ViewCompat; 28 import android.support.v4.view.ViewPropertyAnimatorCompat; 29 import android.support.v4.view.ViewPropertyAnimatorListener; 30 import android.support.v4.view.ViewPropertyAnimatorListenerAdapter; 31 import android.support.v4.widget.ScrollerCompat; 32 import android.support.v7.appcompat.R; 33 import android.support.v7.internal.VersionUtils; 34 import android.support.v7.internal.app.WindowCallback; 35 import android.support.v7.internal.view.menu.MenuPresenter; 36 import android.support.v7.widget.Toolbar; 37 import android.util.AttributeSet; 38 import android.util.SparseArray; 39 import android.view.Menu; 40 import android.view.View; 41 import android.view.ViewGroup; 42 import android.view.Window; 43 44 /** 45 * Special layout for the containing of an overlay action bar (and its content) to correctly handle 46 * fitting system windows when the content has request that its layout ignore them. 47 * 48 * @hide 49 */ 50 public class ActionBarOverlayLayout extends ViewGroup implements DecorContentParent { 51 private static final String TAG = "ActionBarOverlayLayout"; 52 53 private int mActionBarHeight; 54 //private WindowDecorActionBar mActionBar; 55 private int mWindowVisibility = View.VISIBLE; 56 57 // The main UI elements that we handle the layout of. 58 private ContentFrameLayout mContent; 59 private ActionBarContainer mActionBarBottom; 60 private ActionBarContainer mActionBarTop; 61 62 // Some interior UI elements. 63 private DecorToolbar mDecorToolbar; 64 65 // Content overlay drawable - generally the action bar's shadow 66 private Drawable mWindowContentOverlay; 67 private boolean mIgnoreWindowContentOverlay; 68 69 private boolean mOverlayMode; 70 private boolean mHasNonEmbeddedTabs; 71 private boolean mHideOnContentScroll; 72 private boolean mAnimatingForFling; 73 private int mHideOnContentScrollReference; 74 private int mLastSystemUiVisibility; 75 private final Rect mBaseContentInsets = new Rect(); 76 private final Rect mLastBaseContentInsets = new Rect(); 77 private final Rect mContentInsets = new Rect(); 78 private final Rect mBaseInnerInsets = new Rect(); 79 private final Rect mInnerInsets = new Rect(); 80 private final Rect mLastInnerInsets = new Rect(); 81 82 private ActionBarVisibilityCallback mActionBarVisibilityCallback; 83 84 private final int ACTION_BAR_ANIMATE_DELAY = 600; // ms 85 86 private ScrollerCompat mFlingEstimator; 87 88 private ViewPropertyAnimatorCompat mCurrentActionBarTopAnimator; 89 private ViewPropertyAnimatorCompat mCurrentActionBarBottomAnimator; 90 91 private final ViewPropertyAnimatorListener mTopAnimatorListener 92 = new ViewPropertyAnimatorListenerAdapter() { 93 @Override 94 public void onAnimationEnd(View view) { 95 mCurrentActionBarTopAnimator = null; 96 mAnimatingForFling = false; 97 } 98 99 @Override 100 public void onAnimationCancel(View view) { 101 mCurrentActionBarTopAnimator = null; 102 mAnimatingForFling = false; 103 } 104 }; 105 106 private final ViewPropertyAnimatorListener mBottomAnimatorListener = 107 new ViewPropertyAnimatorListenerAdapter() { 108 @Override 109 public void onAnimationEnd(View view) { 110 mCurrentActionBarBottomAnimator = null; 111 mAnimatingForFling = false; 112 } 113 114 @Override 115 public void onAnimationCancel(View view) { 116 mCurrentActionBarBottomAnimator = null; 117 mAnimatingForFling = false; 118 } 119 }; 120 121 private final Runnable mRemoveActionBarHideOffset = new Runnable() { 122 public void run() { 123 haltActionBarHideOffsetAnimations(); 124 mCurrentActionBarTopAnimator = ViewCompat.animate(mActionBarTop).translationY(0) 125 .setListener(mTopAnimatorListener); 126 if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) { 127 mCurrentActionBarBottomAnimator = ViewCompat.animate(mActionBarBottom).translationY(0) 128 .setListener(mBottomAnimatorListener); 129 } 130 } 131 }; 132 133 private final Runnable mAddActionBarHideOffset = new Runnable() { 134 public void run() { 135 haltActionBarHideOffsetAnimations(); 136 mCurrentActionBarTopAnimator = ViewCompat.animate(mActionBarTop) 137 .translationY(-mActionBarTop.getHeight()) 138 .setListener(mTopAnimatorListener); 139 if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) { 140 mCurrentActionBarBottomAnimator = ViewCompat.animate(mActionBarBottom) 141 .translationY(mActionBarBottom.getHeight()) 142 .setListener(mBottomAnimatorListener); 143 } 144 } 145 }; 146 147 // public static final Property<ActionBarOverlayLayout, Integer> ACTION_BAR_HIDE_OFFSET = 148 // new IntProperty<ActionBarOverlayLayout>("actionBarHideOffset") { 149 // 150 // @Override 151 // public void setValue(ActionBarOverlayLayout object, int value) { 152 // object.setActionBarHideOffset(value); 153 // } 154 // 155 // @Override 156 // public Integer get(ActionBarOverlayLayout object) { 157 // return object.getActionBarHideOffset(); 158 // } 159 // }; 160 161 static final int[] ATTRS = new int [] { 162 R.attr.actionBarSize, 163 android.R.attr.windowContentOverlay 164 }; 165 ActionBarOverlayLayout(Context context)166 public ActionBarOverlayLayout(Context context) { 167 super(context); 168 init(context); 169 } 170 ActionBarOverlayLayout(Context context, AttributeSet attrs)171 public ActionBarOverlayLayout(Context context, AttributeSet attrs) { 172 super(context, attrs); 173 init(context); 174 } 175 init(Context context)176 private void init(Context context) { 177 TypedArray ta = getContext().getTheme().obtainStyledAttributes(ATTRS); 178 mActionBarHeight = ta.getDimensionPixelSize(0, 0); 179 mWindowContentOverlay = ta.getDrawable(1); 180 setWillNotDraw(mWindowContentOverlay == null); 181 ta.recycle(); 182 183 mIgnoreWindowContentOverlay = context.getApplicationInfo().targetSdkVersion < 184 Build.VERSION_CODES.KITKAT; 185 186 mFlingEstimator = ScrollerCompat.create(context); 187 } 188 189 @Override 190 protected void onDetachedFromWindow() { 191 super.onDetachedFromWindow(); 192 haltActionBarHideOffsetAnimations(); 193 } 194 195 public void setActionBarVisibilityCallback(ActionBarVisibilityCallback cb) { 196 mActionBarVisibilityCallback = cb; 197 if (getWindowToken() != null) { 198 // This is being initialized after being added to a window; 199 // make sure to update all state now. 200 mActionBarVisibilityCallback.onWindowVisibilityChanged(mWindowVisibility); 201 if (mLastSystemUiVisibility != 0) { 202 int newVis = mLastSystemUiVisibility; 203 onWindowSystemUiVisibilityChanged(newVis); 204 ViewCompat.requestApplyInsets(this); 205 } 206 } 207 } 208 209 public void setOverlayMode(boolean overlayMode) { 210 mOverlayMode = overlayMode; 211 212 /* 213 * Drawing the window content overlay was broken before K so starting to draw it 214 * again unexpectedly will cause artifacts in some apps. They should fix it. 215 */ 216 mIgnoreWindowContentOverlay = overlayMode && 217 getContext().getApplicationInfo().targetSdkVersion < 218 Build.VERSION_CODES.KITKAT; 219 } 220 221 public boolean isInOverlayMode() { 222 return mOverlayMode; 223 } 224 225 public void setHasNonEmbeddedTabs(boolean hasNonEmbeddedTabs) { 226 mHasNonEmbeddedTabs = hasNonEmbeddedTabs; 227 } 228 229 public void setShowingForActionMode(boolean showing) { 230 // TODO: Add workaround for this 231 // if (showing) { 232 // // Here's a fun hack: if the status bar is currently being hidden, 233 // // and the application has asked for stable content insets, then 234 // // we will end up with the action mode action bar being shown 235 // // without the status bar, but moved below where the status bar 236 // // would be. Not nice. Trying to have this be positioned 237 // // correctly is not easy (basically we need yet *another* content 238 // // inset from the window manager to know where to put it), so 239 // // instead we will just temporarily force the status bar to be shown. 240 // if ((getWindowSystemUiVisibility() & (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 241 // | SYSTEM_UI_FLAG_LAYOUT_STABLE)) 242 // == (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_STABLE)) { 243 // setDisabledSystemUiVisibility(SYSTEM_UI_FLAG_FULLSCREEN); 244 // } 245 // } else { 246 // setDisabledSystemUiVisibility(0); 247 // } 248 } 249 250 protected void onConfigurationChanged(Configuration newConfig) { 251 if (Build.VERSION.SDK_INT >= 8) { 252 super.onConfigurationChanged(newConfig); 253 } 254 init(getContext()); 255 ViewCompat.requestApplyInsets(this); 256 } 257 258 public void onWindowSystemUiVisibilityChanged(int visible) { 259 if (Build.VERSION.SDK_INT >= 16) { 260 super.onWindowSystemUiVisibilityChanged(visible); 261 } 262 pullChildren(); 263 final int diff = mLastSystemUiVisibility ^ visible; 264 mLastSystemUiVisibility = visible; 265 final boolean barVisible = (visible & SYSTEM_UI_FLAG_FULLSCREEN) == 0; 266 final boolean stable = (visible & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0; 267 if (mActionBarVisibilityCallback != null) { 268 // We want the bar to be visible if it is not being hidden, 269 // or the app has not turned on a stable UI mode (meaning they 270 // are performing explicit layout around the action bar). 271 mActionBarVisibilityCallback.enableContentAnimations(!stable); 272 if (barVisible || !stable) mActionBarVisibilityCallback.showForSystem(); 273 else mActionBarVisibilityCallback.hideForSystem(); 274 } 275 if ((diff & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) { 276 if (mActionBarVisibilityCallback != null) { 277 ViewCompat.requestApplyInsets(this); 278 } 279 } 280 } 281 282 @Override onWindowVisibilityChanged(int visibility)283 protected void onWindowVisibilityChanged(int visibility) { 284 super.onWindowVisibilityChanged(visibility); 285 mWindowVisibility = visibility; 286 if (mActionBarVisibilityCallback != null) { 287 mActionBarVisibilityCallback.onWindowVisibilityChanged(visibility); 288 } 289 } 290 applyInsets(View view, Rect insets, boolean left, boolean top, boolean bottom, boolean right)291 private boolean applyInsets(View view, Rect insets, boolean left, boolean top, 292 boolean bottom, boolean right) { 293 boolean changed = false; 294 LayoutParams lp = (LayoutParams)view.getLayoutParams(); 295 if (left && lp.leftMargin != insets.left) { 296 changed = true; 297 lp.leftMargin = insets.left; 298 } 299 if (top && lp.topMargin != insets.top) { 300 changed = true; 301 lp.topMargin = insets.top; 302 } 303 if (right && lp.rightMargin != insets.right) { 304 changed = true; 305 lp.rightMargin = insets.right; 306 } 307 if (bottom && lp.bottomMargin != insets.bottom) { 308 changed = true; 309 lp.bottomMargin = insets.bottom; 310 } 311 return changed; 312 } 313 314 @Override fitSystemWindows(Rect insets)315 protected boolean fitSystemWindows(Rect insets) { 316 pullChildren(); 317 318 final int vis = ViewCompat.getWindowSystemUiVisibility(this); 319 final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0; 320 final Rect systemInsets = insets; 321 322 // The top and bottom action bars are always within the content area. 323 boolean changed = applyInsets(mActionBarTop, systemInsets, true, true, false, true); 324 if (mActionBarBottom != null) { 325 changed |= applyInsets(mActionBarBottom, systemInsets, true, false, true, true); 326 } 327 328 mBaseInnerInsets.set(systemInsets); 329 ViewUtils.computeFitSystemWindows(this, mBaseInnerInsets, mBaseContentInsets); 330 if (!mLastBaseContentInsets.equals(mBaseContentInsets)) { 331 changed = true; 332 mLastBaseContentInsets.set(mBaseContentInsets); 333 } 334 335 if (changed) { 336 requestLayout(); 337 } 338 339 // We don't do any more at this point. To correctly compute the content/inner 340 // insets in all cases, we need to know the measured size of the various action 341 // bar elements. fitSystemWindows() happens before the measure pass, so we can't 342 // do that here. Instead we will take this up in onMeasure(). 343 return true; 344 } 345 346 @Override generateDefaultLayoutParams()347 protected LayoutParams generateDefaultLayoutParams() { 348 return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 349 } 350 351 @Override generateLayoutParams(AttributeSet attrs)352 public LayoutParams generateLayoutParams(AttributeSet attrs) { 353 return new LayoutParams(getContext(), attrs); 354 } 355 356 @Override generateLayoutParams(ViewGroup.LayoutParams p)357 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 358 return new LayoutParams(p); 359 } 360 361 @Override checkLayoutParams(ViewGroup.LayoutParams p)362 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 363 return p instanceof LayoutParams; 364 } 365 366 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)367 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 368 pullChildren(); 369 370 int maxHeight = 0; 371 int maxWidth = 0; 372 int childState = 0; 373 374 int topInset = 0; 375 int bottomInset = 0; 376 377 measureChildWithMargins(mActionBarTop, widthMeasureSpec, 0, heightMeasureSpec, 0); 378 LayoutParams lp = (LayoutParams) mActionBarTop.getLayoutParams(); 379 maxWidth = Math.max(maxWidth, 380 mActionBarTop.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); 381 maxHeight = Math.max(maxHeight, 382 mActionBarTop.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); 383 childState = ViewUtils.combineMeasuredStates(childState, 384 ViewCompat.getMeasuredState(mActionBarTop)); 385 386 // xlarge screen layout doesn't have bottom action bar. 387 if (mActionBarBottom != null) { 388 measureChildWithMargins(mActionBarBottom, widthMeasureSpec, 0, heightMeasureSpec, 0); 389 lp = (LayoutParams) mActionBarBottom.getLayoutParams(); 390 maxWidth = Math.max(maxWidth, 391 mActionBarBottom.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); 392 maxHeight = Math.max(maxHeight, 393 mActionBarBottom.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); 394 childState = ViewUtils.combineMeasuredStates(childState, 395 ViewCompat.getMeasuredState(mActionBarBottom)); 396 } 397 398 final int vis = ViewCompat.getWindowSystemUiVisibility(this); 399 final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0; 400 401 if (stable) { 402 // This is the standard space needed for the action bar. For stable measurement, 403 // we can't depend on the size currently reported by it -- this must remain constant. 404 topInset = mActionBarHeight; 405 if (mHasNonEmbeddedTabs) { 406 final View tabs = mActionBarTop.getTabContainer(); 407 if (tabs != null) { 408 // If tabs are not embedded, increase space on top to account for them. 409 topInset += mActionBarHeight; 410 } 411 } 412 } else if (mActionBarTop.getVisibility() != GONE) { 413 // This is the space needed on top of the window for all of the action bar 414 // and tabs. 415 topInset = mActionBarTop.getMeasuredHeight(); 416 } 417 418 if (mDecorToolbar.isSplit()) { 419 // If action bar is split, adjust bottom insets for it. 420 if (mActionBarBottom != null) { 421 if (stable) { 422 bottomInset = mActionBarHeight; 423 } else { 424 bottomInset = mActionBarBottom.getMeasuredHeight(); 425 } 426 } 427 } 428 429 // If the window has not requested system UI layout flags, we need to 430 // make sure its content is not being covered by system UI... though it 431 // will still be covered by the action bar if they have requested it to 432 // overlay. 433 mContentInsets.set(mBaseContentInsets); 434 mInnerInsets.set(mBaseInnerInsets); 435 if (!mOverlayMode && !stable) { 436 mContentInsets.top += topInset; 437 mContentInsets.bottom += bottomInset; 438 } else { 439 mInnerInsets.top += topInset; 440 mInnerInsets.bottom += bottomInset; 441 } 442 applyInsets(mContent, mContentInsets, true, true, true, true); 443 444 if (!mLastInnerInsets.equals(mInnerInsets)) { 445 // If the inner insets have changed, we need to dispatch this down to 446 // the app's fitSystemWindows(). We do this before measuring the content 447 // view to keep the same semantics as the normal fitSystemWindows() call. 448 mLastInnerInsets.set(mInnerInsets); 449 450 mContent.dispatchFitSystemWindows(mInnerInsets); 451 } 452 453 measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0); 454 lp = (LayoutParams) mContent.getLayoutParams(); 455 maxWidth = Math.max(maxWidth, 456 mContent.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); 457 maxHeight = Math.max(maxHeight, 458 mContent.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); 459 childState = ViewUtils.combineMeasuredStates(childState, 460 ViewCompat.getMeasuredState(mContent)); 461 462 // Account for padding too 463 maxWidth += getPaddingLeft() + getPaddingRight(); 464 maxHeight += getPaddingTop() + getPaddingBottom(); 465 466 // Check against our minimum height and width 467 maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); 468 maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); 469 470 setMeasuredDimension( 471 ViewCompat.resolveSizeAndState(maxWidth, widthMeasureSpec, childState), 472 ViewCompat.resolveSizeAndState(maxHeight, heightMeasureSpec, 473 childState << MEASURED_HEIGHT_STATE_SHIFT)); 474 } 475 476 @Override onLayout(boolean changed, int left, int top, int right, int bottom)477 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 478 final int count = getChildCount(); 479 480 final int parentLeft = getPaddingLeft(); 481 final int parentRight = right - left - getPaddingRight(); 482 483 final int parentTop = getPaddingTop(); 484 final int parentBottom = bottom - top - getPaddingBottom(); 485 486 for (int i = 0; i < count; i++) { 487 final View child = getChildAt(i); 488 if (child.getVisibility() != GONE) { 489 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 490 491 final int width = child.getMeasuredWidth(); 492 final int height = child.getMeasuredHeight(); 493 494 int childLeft = parentLeft + lp.leftMargin; 495 int childTop; 496 if (child == mActionBarBottom) { 497 childTop = parentBottom - height - lp.bottomMargin; 498 } else { 499 childTop = parentTop + lp.topMargin; 500 } 501 502 child.layout(childLeft, childTop, childLeft + width, childTop + height); 503 } 504 } 505 } 506 507 @Override draw(Canvas c)508 public void draw(Canvas c) { 509 super.draw(c); 510 if (mWindowContentOverlay != null && !mIgnoreWindowContentOverlay) { 511 final int top = mActionBarTop.getVisibility() == VISIBLE ? 512 (int) (mActionBarTop.getBottom() + ViewCompat.getTranslationY(mActionBarTop) + 0.5f) 513 : 0; 514 mWindowContentOverlay.setBounds(0, top, getWidth(), 515 top + mWindowContentOverlay.getIntrinsicHeight()); 516 mWindowContentOverlay.draw(c); 517 } 518 } 519 520 @Override shouldDelayChildPressedState()521 public boolean shouldDelayChildPressedState() { 522 return false; 523 } 524 525 @Override onStartNestedScroll(View child, View target, int axes)526 public boolean onStartNestedScroll(View child, View target, int axes) { 527 if ((axes & SCROLL_AXIS_VERTICAL) == 0 || mActionBarTop.getVisibility() != VISIBLE) { 528 return false; 529 } 530 return mHideOnContentScroll; 531 } 532 533 @Override onNestedScrollAccepted(View child, View target, int axes)534 public void onNestedScrollAccepted(View child, View target, int axes) { 535 super.onNestedScrollAccepted(child, target, axes); 536 mHideOnContentScrollReference = getActionBarHideOffset(); 537 haltActionBarHideOffsetAnimations(); 538 if (mActionBarVisibilityCallback != null) { 539 mActionBarVisibilityCallback.onContentScrollStarted(); 540 } 541 } 542 543 @Override onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)544 public void onNestedScroll(View target, int dxConsumed, int dyConsumed, 545 int dxUnconsumed, int dyUnconsumed) { 546 mHideOnContentScrollReference += dyConsumed; 547 setActionBarHideOffset(mHideOnContentScrollReference); 548 } 549 550 @Override onStopNestedScroll(View target)551 public void onStopNestedScroll(View target) { 552 super.onStopNestedScroll(target); 553 if (mHideOnContentScroll && !mAnimatingForFling) { 554 if (mHideOnContentScrollReference <= mActionBarTop.getHeight()) { 555 postRemoveActionBarHideOffset(); 556 } else { 557 postAddActionBarHideOffset(); 558 } 559 } 560 if (mActionBarVisibilityCallback != null) { 561 mActionBarVisibilityCallback.onContentScrollStopped(); 562 } 563 } 564 565 @Override onNestedFling(View target, float velocityX, float velocityY, boolean consumed)566 public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { 567 if (!mHideOnContentScroll || !consumed) { 568 return false; 569 } 570 if (shouldHideActionBarOnFling(velocityX, velocityY)) { 571 addActionBarHideOffset(); 572 } else { 573 removeActionBarHideOffset(); 574 } 575 mAnimatingForFling = true; 576 return true; 577 } 578 pullChildren()579 void pullChildren() { 580 if (mContent == null) { 581 mContent = (ContentFrameLayout) findViewById(R.id.action_bar_activity_content); 582 mActionBarTop = (ActionBarContainer) findViewById(R.id.action_bar_container); 583 mDecorToolbar = getDecorToolbar(findViewById(R.id.action_bar)); 584 mActionBarBottom = (ActionBarContainer) findViewById(R.id.split_action_bar); 585 } 586 } 587 getDecorToolbar(View view)588 private DecorToolbar getDecorToolbar(View view) { 589 if (view instanceof DecorToolbar) { 590 return (DecorToolbar) view; 591 } else if (view instanceof Toolbar) { 592 return ((Toolbar) view).getWrapper(); 593 } else { 594 throw new IllegalStateException("Can't make a decor toolbar out of " + 595 view.getClass().getSimpleName()); 596 } 597 } 598 setHideOnContentScrollEnabled(boolean hideOnContentScroll)599 public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) { 600 if (hideOnContentScroll != mHideOnContentScroll) { 601 mHideOnContentScroll = hideOnContentScroll; 602 if (!hideOnContentScroll) { 603 if (VersionUtils.isAtLeastL()) { 604 stopNestedScroll(); 605 } 606 haltActionBarHideOffsetAnimations(); 607 setActionBarHideOffset(0); 608 } 609 } 610 } 611 isHideOnContentScrollEnabled()612 public boolean isHideOnContentScrollEnabled() { 613 return mHideOnContentScroll; 614 } 615 getActionBarHideOffset()616 public int getActionBarHideOffset() { 617 return mActionBarTop != null ? -((int) ViewCompat.getTranslationY(mActionBarTop)) : 0; 618 } 619 setActionBarHideOffset(int offset)620 public void setActionBarHideOffset(int offset) { 621 haltActionBarHideOffsetAnimations(); 622 final int topHeight = mActionBarTop.getHeight(); 623 offset = Math.max(0, Math.min(offset, topHeight)); 624 ViewCompat.setTranslationY(mActionBarTop, -offset); 625 if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) { 626 // Match the hide offset proportionally for a split bar 627 final float fOffset = (float) offset / topHeight; 628 final int bOffset = (int) (mActionBarBottom.getHeight() * fOffset); 629 ViewCompat.setTranslationY(mActionBarBottom, bOffset); 630 } 631 } 632 haltActionBarHideOffsetAnimations()633 private void haltActionBarHideOffsetAnimations() { 634 removeCallbacks(mRemoveActionBarHideOffset); 635 removeCallbacks(mAddActionBarHideOffset); 636 if (mCurrentActionBarTopAnimator != null) { 637 mCurrentActionBarTopAnimator.cancel(); 638 } 639 if (mCurrentActionBarBottomAnimator != null) { 640 mCurrentActionBarBottomAnimator.cancel(); 641 } 642 } 643 postRemoveActionBarHideOffset()644 private void postRemoveActionBarHideOffset() { 645 haltActionBarHideOffsetAnimations(); 646 postDelayed(mRemoveActionBarHideOffset, ACTION_BAR_ANIMATE_DELAY); 647 } 648 postAddActionBarHideOffset()649 private void postAddActionBarHideOffset() { 650 haltActionBarHideOffsetAnimations(); 651 postDelayed(mAddActionBarHideOffset, ACTION_BAR_ANIMATE_DELAY); 652 } 653 removeActionBarHideOffset()654 private void removeActionBarHideOffset() { 655 haltActionBarHideOffsetAnimations(); 656 mRemoveActionBarHideOffset.run(); 657 } 658 addActionBarHideOffset()659 private void addActionBarHideOffset() { 660 haltActionBarHideOffsetAnimations(); 661 mAddActionBarHideOffset.run(); 662 } 663 shouldHideActionBarOnFling(float velocityX, float velocityY)664 private boolean shouldHideActionBarOnFling(float velocityX, float velocityY) { 665 mFlingEstimator.fling(0, 0, 0, (int) velocityY, 0, 0, Integer.MIN_VALUE, Integer.MAX_VALUE); 666 final int finalY = mFlingEstimator.getFinalY(); 667 return finalY > mActionBarTop.getHeight(); 668 } 669 670 @Override setWindowCallback(WindowCallback cb)671 public void setWindowCallback(WindowCallback cb) { 672 pullChildren(); 673 mDecorToolbar.setWindowCallback(cb); 674 } 675 676 @Override setWindowTitle(CharSequence title)677 public void setWindowTitle(CharSequence title) { 678 pullChildren(); 679 mDecorToolbar.setWindowTitle(title); 680 } 681 682 @Override getTitle()683 public CharSequence getTitle() { 684 pullChildren(); 685 return mDecorToolbar.getTitle(); 686 } 687 688 @Override initFeature(int windowFeature)689 public void initFeature(int windowFeature) { 690 pullChildren(); 691 switch (windowFeature) { 692 case Window.FEATURE_PROGRESS: 693 mDecorToolbar.initProgress(); 694 break; 695 case Window.FEATURE_INDETERMINATE_PROGRESS: 696 mDecorToolbar.initIndeterminateProgress(); 697 break; 698 case Window.FEATURE_ACTION_BAR_OVERLAY: 699 setOverlayMode(true); 700 break; 701 } 702 } 703 704 @Override setUiOptions(int uiOptions)705 public void setUiOptions(int uiOptions) { 706 // Split Action Bar not included. 707 } 708 709 @Override hasIcon()710 public boolean hasIcon() { 711 pullChildren(); 712 return mDecorToolbar.hasIcon(); 713 } 714 715 @Override hasLogo()716 public boolean hasLogo() { 717 pullChildren(); 718 return mDecorToolbar.hasLogo(); 719 } 720 721 @Override setIcon(int resId)722 public void setIcon(int resId) { 723 pullChildren(); 724 mDecorToolbar.setIcon(resId); 725 } 726 727 @Override setIcon(Drawable d)728 public void setIcon(Drawable d) { 729 pullChildren(); 730 mDecorToolbar.setIcon(d); 731 } 732 733 @Override setLogo(int resId)734 public void setLogo(int resId) { 735 pullChildren(); 736 mDecorToolbar.setLogo(resId); 737 } 738 739 @Override canShowOverflowMenu()740 public boolean canShowOverflowMenu() { 741 pullChildren(); 742 return mDecorToolbar.canShowOverflowMenu(); 743 } 744 745 @Override isOverflowMenuShowing()746 public boolean isOverflowMenuShowing() { 747 pullChildren(); 748 return mDecorToolbar.isOverflowMenuShowing(); 749 } 750 751 @Override isOverflowMenuShowPending()752 public boolean isOverflowMenuShowPending() { 753 pullChildren(); 754 return mDecorToolbar.isOverflowMenuShowPending(); 755 } 756 757 @Override showOverflowMenu()758 public boolean showOverflowMenu() { 759 pullChildren(); 760 return mDecorToolbar.showOverflowMenu(); 761 } 762 763 @Override hideOverflowMenu()764 public boolean hideOverflowMenu() { 765 pullChildren(); 766 return mDecorToolbar.hideOverflowMenu(); 767 } 768 769 @Override setMenuPrepared()770 public void setMenuPrepared() { 771 pullChildren(); 772 mDecorToolbar.setMenuPrepared(); 773 } 774 775 @Override setMenu(Menu menu, MenuPresenter.Callback cb)776 public void setMenu(Menu menu, MenuPresenter.Callback cb) { 777 pullChildren(); 778 mDecorToolbar.setMenu(menu, cb); 779 } 780 781 @Override saveToolbarHierarchyState(SparseArray<Parcelable> toolbarStates)782 public void saveToolbarHierarchyState(SparseArray<Parcelable> toolbarStates) { 783 pullChildren(); 784 mDecorToolbar.saveHierarchyState(toolbarStates); 785 } 786 787 @Override restoreToolbarHierarchyState(SparseArray<Parcelable> toolbarStates)788 public void restoreToolbarHierarchyState(SparseArray<Parcelable> toolbarStates) { 789 pullChildren(); 790 mDecorToolbar.restoreHierarchyState(toolbarStates); 791 } 792 793 @Override dismissPopups()794 public void dismissPopups() { 795 pullChildren(); 796 mDecorToolbar.dismissPopupMenus(); 797 } 798 799 public static class LayoutParams extends MarginLayoutParams { LayoutParams(Context c, AttributeSet attrs)800 public LayoutParams(Context c, AttributeSet attrs) { 801 super(c, attrs); 802 } 803 LayoutParams(int width, int height)804 public LayoutParams(int width, int height) { 805 super(width, height); 806 } 807 LayoutParams(ViewGroup.LayoutParams source)808 public LayoutParams(ViewGroup.LayoutParams source) { 809 super(source); 810 } 811 LayoutParams(ViewGroup.MarginLayoutParams source)812 public LayoutParams(ViewGroup.MarginLayoutParams source) { 813 super(source); 814 } 815 } 816 817 public interface ActionBarVisibilityCallback { onWindowVisibilityChanged(int visibility)818 void onWindowVisibilityChanged(int visibility); showForSystem()819 void showForSystem(); hideForSystem()820 void hideForSystem(); enableContentAnimations(boolean enable)821 void enableContentAnimations(boolean enable); onContentScrollStarted()822 void onContentScrollStarted(); onContentScrollStopped()823 void onContentScrollStopped(); 824 } 825 } 826