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