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