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 com.android.internal.widget; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.content.Context; 22 import android.content.pm.ActivityInfo; 23 import android.content.res.Configuration; 24 import android.content.res.TypedArray; 25 import android.graphics.Canvas; 26 import android.graphics.Rect; 27 import android.graphics.drawable.Drawable; 28 import android.os.Build; 29 import android.os.Parcelable; 30 import android.util.AttributeSet; 31 import android.util.IntProperty; 32 import android.util.Log; 33 import android.util.Property; 34 import android.util.SparseArray; 35 import android.view.Menu; 36 import android.view.View; 37 import android.view.ViewGroup; 38 import android.view.ViewPropertyAnimator; 39 import android.view.Window; 40 import android.view.WindowInsets; 41 import android.widget.OverScroller; 42 import android.widget.Toolbar; 43 import com.android.internal.view.menu.MenuPresenter; 44 45 /** 46 * Special layout for the containing of an overlay action bar (and its 47 * content) to correctly handle fitting system windows when the content 48 * has request that its layout ignore them. 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 View 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 WindowInsets mBaseInnerInsets = WindowInsets.CONSUMED; 79 private WindowInsets mLastBaseInnerInsets = WindowInsets.CONSUMED; 80 private WindowInsets mInnerInsets = WindowInsets.CONSUMED; 81 private WindowInsets mLastInnerInsets = WindowInsets.CONSUMED; 82 83 private ActionBarVisibilityCallback mActionBarVisibilityCallback; 84 85 private final int ACTION_BAR_ANIMATE_DELAY = 600; // ms 86 87 private OverScroller mFlingEstimator; 88 89 private ViewPropertyAnimator mCurrentActionBarTopAnimator; 90 private ViewPropertyAnimator mCurrentActionBarBottomAnimator; 91 92 private final Animator.AnimatorListener mTopAnimatorListener = new AnimatorListenerAdapter() { 93 @Override 94 public void onAnimationEnd(Animator animation) { 95 mCurrentActionBarTopAnimator = null; 96 mAnimatingForFling = false; 97 } 98 99 @Override 100 public void onAnimationCancel(Animator animation) { 101 mCurrentActionBarTopAnimator = null; 102 mAnimatingForFling = false; 103 } 104 }; 105 106 private final Animator.AnimatorListener mBottomAnimatorListener = 107 new AnimatorListenerAdapter() { 108 @Override 109 public void onAnimationEnd(Animator animation) { 110 mCurrentActionBarBottomAnimator = null; 111 mAnimatingForFling = false; 112 } 113 114 @Override 115 public void onAnimationCancel(Animator animation) { 116 mCurrentActionBarBottomAnimator = null; 117 mAnimatingForFling = false; 118 } 119 }; 120 121 private final Runnable mRemoveActionBarHideOffset = new Runnable() { 122 public void run() { 123 haltActionBarHideOffsetAnimations(); 124 mCurrentActionBarTopAnimator = mActionBarTop.animate().translationY(0) 125 .setListener(mTopAnimatorListener); 126 if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) { 127 mCurrentActionBarBottomAnimator = mActionBarBottom.animate().translationY(0) 128 .setListener(mBottomAnimatorListener); 129 } 130 } 131 }; 132 133 private final Runnable mAddActionBarHideOffset = new Runnable() { 134 public void run() { 135 haltActionBarHideOffsetAnimations(); 136 mCurrentActionBarTopAnimator = mActionBarTop.animate() 137 .translationY(-mActionBarTop.getHeight()) 138 .setListener(mTopAnimatorListener); 139 if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) { 140 mCurrentActionBarBottomAnimator = mActionBarBottom.animate() 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 com.android.internal.R.attr.actionBarSize, 163 com.android.internal.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 = new OverScroller(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 requestApplyInsets(); 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 if (showing) { 231 // Here's a fun hack: if the status bar is currently being hidden, 232 // and the application has asked for stable content insets, then 233 // we will end up with the action mode action bar being shown 234 // without the status bar, but moved below where the status bar 235 // would be. Not nice. Trying to have this be positioned 236 // correctly is not easy (basically we need yet *another* content 237 // inset from the window manager to know where to put it), so 238 // instead we will just temporarily force the status bar to be shown. 239 if ((getWindowSystemUiVisibility() & (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 240 | SYSTEM_UI_FLAG_LAYOUT_STABLE)) 241 == (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_STABLE)) { 242 setDisabledSystemUiVisibility(SYSTEM_UI_FLAG_FULLSCREEN); 243 } 244 } else { 245 setDisabledSystemUiVisibility(0); 246 } 247 } 248 249 @Override 250 protected void onConfigurationChanged(Configuration newConfig) { 251 super.onConfigurationChanged(newConfig); 252 init(getContext()); 253 requestApplyInsets(); 254 } 255 256 @Override 257 public void onWindowSystemUiVisibilityChanged(int visible) { 258 super.onWindowSystemUiVisibilityChanged(visible); 259 pullChildren(); 260 final int diff = mLastSystemUiVisibility ^ visible; 261 mLastSystemUiVisibility = visible; 262 final boolean barVisible = (visible & SYSTEM_UI_FLAG_FULLSCREEN) == 0; 263 final boolean stable = (visible & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0; 264 if (mActionBarVisibilityCallback != null) { 265 // We want the bar to be visible if it is not being hidden, 266 // or the app has not turned on a stable UI mode (meaning they 267 // are performing explicit layout around the action bar). 268 mActionBarVisibilityCallback.enableContentAnimations(!stable); 269 if (barVisible || !stable) mActionBarVisibilityCallback.showForSystem(); 270 else mActionBarVisibilityCallback.hideForSystem(); 271 } 272 if ((diff & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) { 273 if (mActionBarVisibilityCallback != null) { 274 requestApplyInsets(); 275 } 276 } 277 } 278 279 @Override 280 protected void onWindowVisibilityChanged(int visibility) { 281 super.onWindowVisibilityChanged(visibility); 282 mWindowVisibility = visibility; 283 if (mActionBarVisibilityCallback != null) { 284 mActionBarVisibilityCallback.onWindowVisibilityChanged(visibility); 285 } 286 } 287 288 private boolean applyInsets(View view, Rect insets, boolean left, boolean top, 289 boolean bottom, boolean right) { 290 boolean changed = false; 291 LayoutParams lp = (LayoutParams)view.getLayoutParams(); 292 if (left && lp.leftMargin != insets.left) { 293 changed = true; 294 lp.leftMargin = insets.left; 295 } 296 if (top && lp.topMargin != insets.top) { 297 changed = true; 298 lp.topMargin = insets.top; 299 } 300 if (right && lp.rightMargin != insets.right) { 301 changed = true; 302 lp.rightMargin = insets.right; 303 } 304 if (bottom && lp.bottomMargin != insets.bottom) { 305 changed = true; 306 lp.bottomMargin = insets.bottom; 307 } 308 return changed; 309 } 310 311 @Override 312 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 313 pullChildren(); 314 315 final int vis = getWindowSystemUiVisibility(); 316 final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0; 317 final Rect systemInsets = insets.getSystemWindowInsets(); 318 319 // The top and bottom action bars are always within the content area. 320 boolean changed = applyInsets(mActionBarTop, systemInsets, true, true, false, true); 321 if (mActionBarBottom != null) { 322 changed |= applyInsets(mActionBarBottom, systemInsets, true, false, true, true); 323 } 324 325 // Cannot use the result of computeSystemWindowInsets, because that consumes the 326 // systemWindowInsets. Instead, we do the insetting by the local insets ourselves. 327 computeSystemWindowInsets(insets, mBaseContentInsets); 328 mBaseInnerInsets = insets.inset(mBaseContentInsets); 329 330 if (!mLastBaseInnerInsets.equals(mBaseInnerInsets)) { 331 changed = true; 332 mLastBaseInnerInsets = mBaseInnerInsets; 333 } 334 if (!mLastBaseContentInsets.equals(mBaseContentInsets)) { 335 changed = true; 336 mLastBaseContentInsets.set(mBaseContentInsets); 337 } 338 339 if (changed) { 340 requestLayout(); 341 } 342 343 // We don't do any more at this point. To correctly compute the content/inner 344 // insets in all cases, we need to know the measured size of the various action 345 // bar elements. onApplyWindowInsets() happens before the measure pass, so we can't 346 // do that here. Instead we will take this up in onMeasure(). 347 return WindowInsets.CONSUMED; 348 } 349 350 @Override 351 protected LayoutParams generateDefaultLayoutParams() { 352 return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 353 } 354 355 @Override 356 public LayoutParams generateLayoutParams(AttributeSet attrs) { 357 return new LayoutParams(getContext(), attrs); 358 } 359 360 @Override 361 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 362 return new LayoutParams(p); 363 } 364 365 @Override 366 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 367 return p instanceof LayoutParams; 368 } 369 370 @Override 371 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 372 pullChildren(); 373 374 int maxHeight = 0; 375 int maxWidth = 0; 376 int childState = 0; 377 378 int topInset = 0; 379 int bottomInset = 0; 380 381 measureChildWithMargins(mActionBarTop, widthMeasureSpec, 0, heightMeasureSpec, 0); 382 LayoutParams lp = (LayoutParams) mActionBarTop.getLayoutParams(); 383 maxWidth = Math.max(maxWidth, 384 mActionBarTop.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); 385 maxHeight = Math.max(maxHeight, 386 mActionBarTop.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); 387 childState = combineMeasuredStates(childState, mActionBarTop.getMeasuredState()); 388 389 // xlarge screen layout doesn't have bottom action bar. 390 if (mActionBarBottom != null) { 391 measureChildWithMargins(mActionBarBottom, widthMeasureSpec, 0, heightMeasureSpec, 0); 392 lp = (LayoutParams) mActionBarBottom.getLayoutParams(); 393 maxWidth = Math.max(maxWidth, 394 mActionBarBottom.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); 395 maxHeight = Math.max(maxHeight, 396 mActionBarBottom.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); 397 childState = combineMeasuredStates(childState, mActionBarBottom.getMeasuredState()); 398 } 399 400 final int vis = getWindowSystemUiVisibility(); 401 final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0; 402 403 if (stable) { 404 // This is the standard space needed for the action bar. For stable measurement, 405 // we can't depend on the size currently reported by it -- this must remain constant. 406 topInset = mActionBarHeight; 407 if (mHasNonEmbeddedTabs) { 408 final View tabs = mActionBarTop.getTabContainer(); 409 if (tabs != null) { 410 // If tabs are not embedded, increase space on top to account for them. 411 topInset += mActionBarHeight; 412 } 413 } 414 } else if (mActionBarTop.getVisibility() != GONE) { 415 // This is the space needed on top of the window for all of the action bar 416 // and tabs. 417 topInset = mActionBarTop.getMeasuredHeight(); 418 } 419 420 if (mDecorToolbar.isSplit()) { 421 // If action bar is split, adjust bottom insets for it. 422 if (mActionBarBottom != null) { 423 if (stable) { 424 bottomInset = mActionBarHeight; 425 } else { 426 bottomInset = mActionBarBottom.getMeasuredHeight(); 427 } 428 } 429 } 430 431 // If the window has not requested system UI layout flags, we need to 432 // make sure its content is not being covered by system UI... though it 433 // will still be covered by the action bar if they have requested it to 434 // overlay. 435 mContentInsets.set(mBaseContentInsets); 436 mInnerInsets = mBaseInnerInsets; 437 if (!mOverlayMode && !stable) { 438 mContentInsets.top += topInset; 439 mContentInsets.bottom += bottomInset; 440 // Content view has been shrunk, shrink all insets to match. 441 mInnerInsets = mInnerInsets.inset(0 /* left */, topInset, 0 /* right */, bottomInset); 442 } else { 443 // Add ActionBar to system window inset, but leave other insets untouched. 444 mInnerInsets = mInnerInsets.replaceSystemWindowInsets( 445 mInnerInsets.getSystemWindowInsetLeft(), 446 mInnerInsets.getSystemWindowInsetTop() + topInset, 447 mInnerInsets.getSystemWindowInsetRight(), 448 mInnerInsets.getSystemWindowInsetBottom() + bottomInset 449 ); 450 } 451 applyInsets(mContent, mContentInsets, true, true, true, true); 452 453 if (!mLastInnerInsets.equals(mInnerInsets)) { 454 // If the inner insets have changed, we need to dispatch this down to 455 // the app's onApplyWindowInsets(). We do this before measuring the content 456 // view to keep the same semantics as the normal fitSystemWindows() call. 457 mLastInnerInsets = mInnerInsets; 458 mContent.dispatchApplyWindowInsets(mInnerInsets); 459 } 460 461 measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0); 462 lp = (LayoutParams) mContent.getLayoutParams(); 463 maxWidth = Math.max(maxWidth, 464 mContent.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); 465 maxHeight = Math.max(maxHeight, 466 mContent.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); 467 childState = combineMeasuredStates(childState, mContent.getMeasuredState()); 468 469 // Account for padding too 470 maxWidth += getPaddingLeft() + getPaddingRight(); 471 maxHeight += getPaddingTop() + getPaddingBottom(); 472 473 // Check against our minimum height and width 474 maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); 475 maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); 476 477 setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), 478 resolveSizeAndState(maxHeight, heightMeasureSpec, 479 childState << MEASURED_HEIGHT_STATE_SHIFT)); 480 } 481 482 @Override 483 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 484 final int count = getChildCount(); 485 486 final int parentLeft = getPaddingLeft(); 487 final int parentRight = right - left - getPaddingRight(); 488 489 final int parentTop = getPaddingTop(); 490 final int parentBottom = bottom - top - getPaddingBottom(); 491 492 for (int i = 0; i < count; i++) { 493 final View child = getChildAt(i); 494 if (child.getVisibility() != GONE) { 495 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 496 497 final int width = child.getMeasuredWidth(); 498 final int height = child.getMeasuredHeight(); 499 500 int childLeft = parentLeft + lp.leftMargin; 501 int childTop; 502 if (child == mActionBarBottom) { 503 childTop = parentBottom - height - lp.bottomMargin; 504 } else { 505 childTop = parentTop + lp.topMargin; 506 } 507 508 child.layout(childLeft, childTop, childLeft + width, childTop + height); 509 } 510 } 511 } 512 513 @Override 514 public void draw(Canvas c) { 515 super.draw(c); 516 if (mWindowContentOverlay != null && !mIgnoreWindowContentOverlay) { 517 final int top = mActionBarTop.getVisibility() == VISIBLE ? 518 (int) (mActionBarTop.getBottom() + mActionBarTop.getTranslationY() + 0.5f) : 0; 519 mWindowContentOverlay.setBounds(0, top, getWidth(), 520 top + mWindowContentOverlay.getIntrinsicHeight()); 521 mWindowContentOverlay.draw(c); 522 } 523 } 524 525 @Override 526 public boolean shouldDelayChildPressedState() { 527 return false; 528 } 529 530 @Override 531 public boolean onStartNestedScroll(View child, View target, int axes) { 532 if ((axes & SCROLL_AXIS_VERTICAL) == 0 || mActionBarTop.getVisibility() != VISIBLE) { 533 return false; 534 } 535 return mHideOnContentScroll; 536 } 537 538 @Override 539 public void onNestedScrollAccepted(View child, View target, int axes) { 540 super.onNestedScrollAccepted(child, target, axes); 541 mHideOnContentScrollReference = getActionBarHideOffset(); 542 haltActionBarHideOffsetAnimations(); 543 if (mActionBarVisibilityCallback != null) { 544 mActionBarVisibilityCallback.onContentScrollStarted(); 545 } 546 } 547 548 @Override 549 public void onNestedScroll(View target, int dxConsumed, int dyConsumed, 550 int dxUnconsumed, int dyUnconsumed) { 551 mHideOnContentScrollReference += dyConsumed; 552 setActionBarHideOffset(mHideOnContentScrollReference); 553 } 554 555 @Override 556 public void onStopNestedScroll(View target) { 557 super.onStopNestedScroll(target); 558 if (mHideOnContentScroll && !mAnimatingForFling) { 559 if (mHideOnContentScrollReference <= mActionBarTop.getHeight()) { 560 postRemoveActionBarHideOffset(); 561 } else { 562 postAddActionBarHideOffset(); 563 } 564 } 565 if (mActionBarVisibilityCallback != null) { 566 mActionBarVisibilityCallback.onContentScrollStopped(); 567 } 568 } 569 570 @Override 571 public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { 572 if (!mHideOnContentScroll || !consumed) { 573 return false; 574 } 575 if (shouldHideActionBarOnFling(velocityX, velocityY)) { 576 addActionBarHideOffset(); 577 } else { 578 removeActionBarHideOffset(); 579 } 580 mAnimatingForFling = true; 581 return true; 582 } 583 584 void pullChildren() { 585 if (mContent == null) { 586 mContent = findViewById(com.android.internal.R.id.content); 587 mActionBarTop = findViewById( 588 com.android.internal.R.id.action_bar_container); 589 mDecorToolbar = getDecorToolbar(findViewById(com.android.internal.R.id.action_bar)); 590 mActionBarBottom = findViewById( 591 com.android.internal.R.id.split_action_bar); 592 } 593 } 594 595 private DecorToolbar getDecorToolbar(View view) { 596 if (view instanceof DecorToolbar) { 597 return (DecorToolbar) view; 598 } else if (view instanceof Toolbar) { 599 return ((Toolbar) view).getWrapper(); 600 } else { 601 throw new IllegalStateException("Can't make a decor toolbar out of " + 602 view.getClass().getSimpleName()); 603 } 604 } 605 606 public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) { 607 if (hideOnContentScroll != mHideOnContentScroll) { 608 mHideOnContentScroll = hideOnContentScroll; 609 if (!hideOnContentScroll) { 610 stopNestedScroll(); 611 haltActionBarHideOffsetAnimations(); 612 setActionBarHideOffset(0); 613 } 614 } 615 } 616 617 public boolean isHideOnContentScrollEnabled() { 618 return mHideOnContentScroll; 619 } 620 621 public int getActionBarHideOffset() { 622 return mActionBarTop != null ? -((int) mActionBarTop.getTranslationY()) : 0; 623 } 624 625 public void setActionBarHideOffset(int offset) { 626 haltActionBarHideOffsetAnimations(); 627 final int topHeight = mActionBarTop.getHeight(); 628 offset = Math.max(0, Math.min(offset, topHeight)); 629 mActionBarTop.setTranslationY(-offset); 630 if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) { 631 // Match the hide offset proportionally for a split bar 632 final float fOffset = (float) offset / topHeight; 633 final int bOffset = (int) (mActionBarBottom.getHeight() * fOffset); 634 mActionBarBottom.setTranslationY(bOffset); 635 } 636 } 637 638 private void haltActionBarHideOffsetAnimations() { 639 removeCallbacks(mRemoveActionBarHideOffset); 640 removeCallbacks(mAddActionBarHideOffset); 641 if (mCurrentActionBarTopAnimator != null) { 642 mCurrentActionBarTopAnimator.cancel(); 643 } 644 if (mCurrentActionBarBottomAnimator != null) { 645 mCurrentActionBarBottomAnimator.cancel(); 646 } 647 } 648 649 private void postRemoveActionBarHideOffset() { 650 haltActionBarHideOffsetAnimations(); 651 postDelayed(mRemoveActionBarHideOffset, ACTION_BAR_ANIMATE_DELAY); 652 } 653 654 private void postAddActionBarHideOffset() { 655 haltActionBarHideOffsetAnimations(); 656 postDelayed(mAddActionBarHideOffset, ACTION_BAR_ANIMATE_DELAY); 657 } 658 659 private void removeActionBarHideOffset() { 660 haltActionBarHideOffsetAnimations(); 661 mRemoveActionBarHideOffset.run(); 662 } 663 664 private void addActionBarHideOffset() { 665 haltActionBarHideOffsetAnimations(); 666 mAddActionBarHideOffset.run(); 667 } 668 669 private boolean shouldHideActionBarOnFling(float velocityX, float velocityY) { 670 mFlingEstimator.fling(0, 0, 0, (int) velocityY, 0, 0, Integer.MIN_VALUE, Integer.MAX_VALUE); 671 final int finalY = mFlingEstimator.getFinalY(); 672 return finalY > mActionBarTop.getHeight(); 673 } 674 675 @Override 676 public void setWindowCallback(Window.Callback cb) { 677 pullChildren(); 678 mDecorToolbar.setWindowCallback(cb); 679 } 680 681 @Override 682 public void setWindowTitle(CharSequence title) { 683 pullChildren(); 684 mDecorToolbar.setWindowTitle(title); 685 } 686 687 @Override 688 public CharSequence getTitle() { 689 pullChildren(); 690 return mDecorToolbar.getTitle(); 691 } 692 693 @Override 694 public void initFeature(int windowFeature) { 695 pullChildren(); 696 switch (windowFeature) { 697 case Window.FEATURE_PROGRESS: 698 mDecorToolbar.initProgress(); 699 break; 700 case Window.FEATURE_INDETERMINATE_PROGRESS: 701 mDecorToolbar.initIndeterminateProgress(); 702 break; 703 case Window.FEATURE_ACTION_BAR_OVERLAY: 704 setOverlayMode(true); 705 break; 706 } 707 } 708 709 @Override 710 public void setUiOptions(int uiOptions) { 711 boolean splitActionBar = false; 712 final boolean splitWhenNarrow = 713 (uiOptions & ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW) != 0; 714 if (splitWhenNarrow) { 715 splitActionBar = getContext().getResources().getBoolean( 716 com.android.internal.R.bool.split_action_bar_is_narrow); 717 } 718 if (splitActionBar) { 719 pullChildren(); 720 if (mActionBarBottom != null && mDecorToolbar.canSplit()) { 721 mDecorToolbar.setSplitView(mActionBarBottom); 722 mDecorToolbar.setSplitToolbar(splitActionBar); 723 mDecorToolbar.setSplitWhenNarrow(splitWhenNarrow); 724 725 final ActionBarContextView cab = findViewById( 726 com.android.internal.R.id.action_context_bar); 727 cab.setSplitView(mActionBarBottom); 728 cab.setSplitToolbar(splitActionBar); 729 cab.setSplitWhenNarrow(splitWhenNarrow); 730 } else if (splitActionBar) { 731 Log.e(TAG, "Requested split action bar with " + 732 "incompatible window decor! Ignoring request."); 733 } 734 } 735 } 736 737 @Override 738 public boolean hasIcon() { 739 pullChildren(); 740 return mDecorToolbar.hasIcon(); 741 } 742 743 @Override 744 public boolean hasLogo() { 745 pullChildren(); 746 return mDecorToolbar.hasLogo(); 747 } 748 749 @Override 750 public void setIcon(int resId) { 751 pullChildren(); 752 mDecorToolbar.setIcon(resId); 753 } 754 755 @Override 756 public void setIcon(Drawable d) { 757 pullChildren(); 758 mDecorToolbar.setIcon(d); 759 } 760 761 @Override 762 public void setLogo(int resId) { 763 pullChildren(); 764 mDecorToolbar.setLogo(resId); 765 } 766 767 @Override 768 public boolean canShowOverflowMenu() { 769 pullChildren(); 770 return mDecorToolbar.canShowOverflowMenu(); 771 } 772 773 @Override 774 public boolean isOverflowMenuShowing() { 775 pullChildren(); 776 return mDecorToolbar.isOverflowMenuShowing(); 777 } 778 779 @Override 780 public boolean isOverflowMenuShowPending() { 781 pullChildren(); 782 return mDecorToolbar.isOverflowMenuShowPending(); 783 } 784 785 @Override 786 public boolean showOverflowMenu() { 787 pullChildren(); 788 return mDecorToolbar.showOverflowMenu(); 789 } 790 791 @Override 792 public boolean hideOverflowMenu() { 793 pullChildren(); 794 return mDecorToolbar.hideOverflowMenu(); 795 } 796 797 @Override 798 public void setMenuPrepared() { 799 pullChildren(); 800 mDecorToolbar.setMenuPrepared(); 801 } 802 803 @Override 804 public void setMenu(Menu menu, MenuPresenter.Callback cb) { 805 pullChildren(); 806 mDecorToolbar.setMenu(menu, cb); 807 } 808 809 @Override 810 public void saveToolbarHierarchyState(SparseArray<Parcelable> toolbarStates) { 811 pullChildren(); 812 mDecorToolbar.saveHierarchyState(toolbarStates); 813 } 814 815 @Override 816 public void restoreToolbarHierarchyState(SparseArray<Parcelable> toolbarStates) { 817 pullChildren(); 818 mDecorToolbar.restoreHierarchyState(toolbarStates); 819 } 820 821 @Override 822 public void dismissPopups() { 823 pullChildren(); 824 mDecorToolbar.dismissPopupMenus(); 825 } 826 827 public static class LayoutParams extends MarginLayoutParams { 828 public LayoutParams(Context c, AttributeSet attrs) { 829 super(c, attrs); 830 } 831 832 public LayoutParams(int width, int height) { 833 super(width, height); 834 } 835 836 public LayoutParams(ViewGroup.LayoutParams source) { 837 super(source); 838 } 839 840 public LayoutParams(ViewGroup.MarginLayoutParams source) { 841 super(source); 842 } 843 } 844 845 public interface ActionBarVisibilityCallback { 846 void onWindowVisibilityChanged(int visibility); 847 void showForSystem(); 848 void hideForSystem(); 849 void enableContentAnimations(boolean enable); 850 void onContentScrollStarted(); 851 void onContentScrollStopped(); 852 } 853 } 854