1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.support.v7.app; 18 19import android.app.Activity; 20import android.app.Dialog; 21import android.content.Context; 22import android.content.res.Configuration; 23import android.content.res.Resources; 24import android.content.res.TypedArray; 25import android.graphics.drawable.Drawable; 26import android.os.Build; 27import android.support.v4.app.FragmentActivity; 28import android.support.v4.app.FragmentTransaction; 29import android.support.v4.view.ViewCompat; 30import android.support.v4.view.ViewPropertyAnimatorCompat; 31import android.support.v4.view.ViewPropertyAnimatorListener; 32import android.support.v4.view.ViewPropertyAnimatorListenerAdapter; 33import android.support.v4.view.ViewPropertyAnimatorUpdateListener; 34import android.support.v7.appcompat.R; 35import android.support.v7.view.ActionBarPolicy; 36import android.support.v7.view.ActionMode; 37import android.support.v7.view.SupportMenuInflater; 38import android.support.v7.view.ViewPropertyAnimatorCompatSet; 39import android.support.v7.view.menu.MenuBuilder; 40import android.support.v7.view.menu.MenuPopupHelper; 41import android.support.v7.view.menu.SubMenuBuilder; 42import android.support.v7.widget.ActionBarContainer; 43import android.support.v7.widget.ActionBarContextView; 44import android.support.v7.widget.ActionBarOverlayLayout; 45import android.support.v7.widget.AppCompatDrawableManager; 46import android.support.v7.widget.DecorToolbar; 47import android.support.v7.widget.ScrollingTabContainerView; 48import android.support.v7.widget.Toolbar; 49import android.util.TypedValue; 50import android.view.ContextThemeWrapper; 51import android.view.LayoutInflater; 52import android.view.Menu; 53import android.view.MenuInflater; 54import android.view.MenuItem; 55import android.view.View; 56import android.view.ViewGroup; 57import android.view.ViewParent; 58import android.view.Window; 59import android.view.accessibility.AccessibilityEvent; 60import android.view.animation.AccelerateInterpolator; 61import android.view.animation.DecelerateInterpolator; 62import android.view.animation.Interpolator; 63import android.widget.SpinnerAdapter; 64 65import java.lang.ref.WeakReference; 66import java.util.ArrayList; 67 68/** 69 * WindowDecorActionBar is the ActionBar implementation used 70 * by devices of all screen sizes as part of the window decor layout. 71 * 72 * @hide 73 */ 74public class WindowDecorActionBar extends ActionBar implements 75 ActionBarOverlayLayout.ActionBarVisibilityCallback { 76 private static final String TAG = "WindowDecorActionBar"; 77 78 private static final Interpolator sHideInterpolator = new AccelerateInterpolator(); 79 private static final Interpolator sShowInterpolator = new DecelerateInterpolator(); 80 81 /** 82 * Only allow show/hide animations on ICS+, as that is what ViewPropertyAnimatorCompat supports 83 */ 84 private static final boolean ALLOW_SHOW_HIDE_ANIMATIONS = Build.VERSION.SDK_INT >= 14; 85 86 private Context mContext; 87 private Context mThemedContext; 88 private Activity mActivity; 89 private Dialog mDialog; 90 91 private ActionBarOverlayLayout mOverlayLayout; 92 private ActionBarContainer mContainerView; 93 private DecorToolbar mDecorToolbar; 94 private ActionBarContextView mContextView; 95 private View mContentView; 96 private ScrollingTabContainerView mTabScrollView; 97 98 private ArrayList<TabImpl> mTabs = new ArrayList<TabImpl>(); 99 100 private TabImpl mSelectedTab; 101 private int mSavedTabPosition = INVALID_POSITION; 102 103 private boolean mDisplayHomeAsUpSet; 104 105 ActionModeImpl mActionMode; 106 ActionMode mDeferredDestroyActionMode; 107 ActionMode.Callback mDeferredModeDestroyCallback; 108 109 private boolean mLastMenuVisibility; 110 private ArrayList<OnMenuVisibilityListener> mMenuVisibilityListeners = 111 new ArrayList<OnMenuVisibilityListener>(); 112 113 private static final int INVALID_POSITION = -1; 114 115 // The fade duration for toolbar and action bar when entering/exiting action mode. 116 private static final long FADE_OUT_DURATION_MS = 100; 117 private static final long FADE_IN_DURATION_MS = 200; 118 119 private boolean mHasEmbeddedTabs; 120 121 private int mCurWindowVisibility = View.VISIBLE; 122 123 private boolean mContentAnimations = true; 124 private boolean mHiddenByApp; 125 private boolean mHiddenBySystem; 126 private boolean mShowingForMode; 127 128 private boolean mNowShowing = true; 129 130 private ViewPropertyAnimatorCompatSet mCurrentShowAnim; 131 private boolean mShowHideAnimationEnabled; 132 boolean mHideOnContentScroll; 133 134 final ViewPropertyAnimatorListener mHideListener = new ViewPropertyAnimatorListenerAdapter() { 135 @Override 136 public void onAnimationEnd(View view) { 137 if (mContentAnimations && mContentView != null) { 138 ViewCompat.setTranslationY(mContentView, 0f); 139 ViewCompat.setTranslationY(mContainerView, 0f); 140 } 141 mContainerView.setVisibility(View.GONE); 142 mContainerView.setTransitioning(false); 143 mCurrentShowAnim = null; 144 completeDeferredDestroyActionMode(); 145 if (mOverlayLayout != null) { 146 ViewCompat.requestApplyInsets(mOverlayLayout); 147 } 148 } 149 }; 150 151 final ViewPropertyAnimatorListener mShowListener = new ViewPropertyAnimatorListenerAdapter() { 152 @Override 153 public void onAnimationEnd(View view) { 154 mCurrentShowAnim = null; 155 mContainerView.requestLayout(); 156 } 157 }; 158 159 final ViewPropertyAnimatorUpdateListener mUpdateListener = 160 new ViewPropertyAnimatorUpdateListener() { 161 @Override 162 public void onAnimationUpdate(View view) { 163 final ViewParent parent = mContainerView.getParent(); 164 ((View) parent).invalidate(); 165 } 166 }; 167 168 public WindowDecorActionBar(Activity activity, boolean overlayMode) { 169 mActivity = activity; 170 Window window = activity.getWindow(); 171 View decor = window.getDecorView(); 172 init(decor); 173 if (!overlayMode) { 174 mContentView = decor.findViewById(android.R.id.content); 175 } 176 } 177 178 public WindowDecorActionBar(Dialog dialog) { 179 mDialog = dialog; 180 init(dialog.getWindow().getDecorView()); 181 } 182 183 /** 184 * Only for edit mode. 185 * @hide 186 */ 187 public WindowDecorActionBar(View layout) { 188 assert layout.isInEditMode(); 189 init(layout); 190 } 191 192 private void init(View decor) { 193 mOverlayLayout = (ActionBarOverlayLayout) decor.findViewById(R.id.decor_content_parent); 194 if (mOverlayLayout != null) { 195 mOverlayLayout.setActionBarVisibilityCallback(this); 196 } 197 mDecorToolbar = getDecorToolbar(decor.findViewById(R.id.action_bar)); 198 mContextView = (ActionBarContextView) decor.findViewById( 199 R.id.action_context_bar); 200 mContainerView = (ActionBarContainer) decor.findViewById( 201 R.id.action_bar_container); 202 203 if (mDecorToolbar == null || mContextView == null || mContainerView == null) { 204 throw new IllegalStateException(getClass().getSimpleName() + " can only be used " + 205 "with a compatible window decor layout"); 206 } 207 208 mContext = mDecorToolbar.getContext(); 209 210 // This was initially read from the action bar style 211 final int current = mDecorToolbar.getDisplayOptions(); 212 final boolean homeAsUp = (current & DISPLAY_HOME_AS_UP) != 0; 213 if (homeAsUp) { 214 mDisplayHomeAsUpSet = true; 215 } 216 217 ActionBarPolicy abp = ActionBarPolicy.get(mContext); 218 setHomeButtonEnabled(abp.enableHomeButtonByDefault() || homeAsUp); 219 setHasEmbeddedTabs(abp.hasEmbeddedTabs()); 220 221 final TypedArray a = mContext.obtainStyledAttributes(null, 222 R.styleable.ActionBar, 223 R.attr.actionBarStyle, 0); 224 if (a.getBoolean(R.styleable.ActionBar_hideOnContentScroll, false)) { 225 setHideOnContentScrollEnabled(true); 226 } 227 final int elevation = a.getDimensionPixelSize(R.styleable.ActionBar_elevation, 0); 228 if (elevation != 0) { 229 setElevation(elevation); 230 } 231 a.recycle(); 232 } 233 234 private DecorToolbar getDecorToolbar(View view) { 235 if (view instanceof DecorToolbar) { 236 return (DecorToolbar) view; 237 } else if (view instanceof Toolbar) { 238 return ((Toolbar) view).getWrapper(); 239 } else { 240 throw new IllegalStateException("Can't make a decor toolbar out of " + 241 view != null ? view.getClass().getSimpleName() : "null"); 242 } 243 } 244 245 @Override 246 public void setElevation(float elevation) { 247 ViewCompat.setElevation(mContainerView, elevation); 248 } 249 250 @Override 251 public float getElevation() { 252 return ViewCompat.getElevation(mContainerView); 253 } 254 255 public void onConfigurationChanged(Configuration newConfig) { 256 setHasEmbeddedTabs(ActionBarPolicy.get(mContext).hasEmbeddedTabs()); 257 } 258 259 private void setHasEmbeddedTabs(boolean hasEmbeddedTabs) { 260 mHasEmbeddedTabs = hasEmbeddedTabs; 261 // Switch tab layout configuration if needed 262 if (!mHasEmbeddedTabs) { 263 mDecorToolbar.setEmbeddedTabView(null); 264 mContainerView.setTabContainer(mTabScrollView); 265 } else { 266 mContainerView.setTabContainer(null); 267 mDecorToolbar.setEmbeddedTabView(mTabScrollView); 268 } 269 final boolean isInTabMode = getNavigationMode() == NAVIGATION_MODE_TABS; 270 if (mTabScrollView != null) { 271 if (isInTabMode) { 272 mTabScrollView.setVisibility(View.VISIBLE); 273 if (mOverlayLayout != null) { 274 ViewCompat.requestApplyInsets(mOverlayLayout); 275 } 276 } else { 277 mTabScrollView.setVisibility(View.GONE); 278 } 279 } 280 mDecorToolbar.setCollapsible(!mHasEmbeddedTabs && isInTabMode); 281 mOverlayLayout.setHasNonEmbeddedTabs(!mHasEmbeddedTabs && isInTabMode); 282 } 283 284 private void ensureTabsExist() { 285 if (mTabScrollView != null) { 286 return; 287 } 288 289 ScrollingTabContainerView tabScroller = new ScrollingTabContainerView(mContext); 290 291 if (mHasEmbeddedTabs) { 292 tabScroller.setVisibility(View.VISIBLE); 293 mDecorToolbar.setEmbeddedTabView(tabScroller); 294 } else { 295 if (getNavigationMode() == NAVIGATION_MODE_TABS) { 296 tabScroller.setVisibility(View.VISIBLE); 297 if (mOverlayLayout != null) { 298 ViewCompat.requestApplyInsets(mOverlayLayout); 299 } 300 } else { 301 tabScroller.setVisibility(View.GONE); 302 } 303 mContainerView.setTabContainer(tabScroller); 304 } 305 mTabScrollView = tabScroller; 306 } 307 308 void completeDeferredDestroyActionMode() { 309 if (mDeferredModeDestroyCallback != null) { 310 mDeferredModeDestroyCallback.onDestroyActionMode(mDeferredDestroyActionMode); 311 mDeferredDestroyActionMode = null; 312 mDeferredModeDestroyCallback = null; 313 } 314 } 315 316 public void onWindowVisibilityChanged(int visibility) { 317 mCurWindowVisibility = visibility; 318 } 319 320 /** 321 * Enables or disables animation between show/hide states. 322 * If animation is disabled using this method, animations in progress 323 * will be finished. 324 * 325 * @param enabled true to animate, false to not animate. 326 */ 327 public void setShowHideAnimationEnabled(boolean enabled) { 328 mShowHideAnimationEnabled = enabled; 329 if (!enabled && mCurrentShowAnim != null) { 330 mCurrentShowAnim.cancel(); 331 } 332 } 333 334 public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) { 335 mMenuVisibilityListeners.add(listener); 336 } 337 338 public void removeOnMenuVisibilityListener(OnMenuVisibilityListener listener) { 339 mMenuVisibilityListeners.remove(listener); 340 } 341 342 public void dispatchMenuVisibilityChanged(boolean isVisible) { 343 if (isVisible == mLastMenuVisibility) { 344 return; 345 } 346 mLastMenuVisibility = isVisible; 347 348 final int count = mMenuVisibilityListeners.size(); 349 for (int i = 0; i < count; i++) { 350 mMenuVisibilityListeners.get(i).onMenuVisibilityChanged(isVisible); 351 } 352 } 353 354 @Override 355 public void setCustomView(int resId) { 356 setCustomView(LayoutInflater.from(getThemedContext()).inflate(resId, 357 mDecorToolbar.getViewGroup(), false)); 358 } 359 360 @Override 361 public void setDisplayUseLogoEnabled(boolean useLogo) { 362 setDisplayOptions(useLogo ? DISPLAY_USE_LOGO : 0, DISPLAY_USE_LOGO); 363 } 364 365 @Override 366 public void setDisplayShowHomeEnabled(boolean showHome) { 367 setDisplayOptions(showHome ? DISPLAY_SHOW_HOME : 0, DISPLAY_SHOW_HOME); 368 } 369 370 @Override 371 public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) { 372 setDisplayOptions(showHomeAsUp ? DISPLAY_HOME_AS_UP : 0, DISPLAY_HOME_AS_UP); 373 } 374 375 @Override 376 public void setDisplayShowTitleEnabled(boolean showTitle) { 377 setDisplayOptions(showTitle ? DISPLAY_SHOW_TITLE : 0, DISPLAY_SHOW_TITLE); 378 } 379 380 @Override 381 public void setDisplayShowCustomEnabled(boolean showCustom) { 382 setDisplayOptions(showCustom ? DISPLAY_SHOW_CUSTOM : 0, DISPLAY_SHOW_CUSTOM); 383 } 384 385 @Override 386 public void setHomeButtonEnabled(boolean enable) { 387 mDecorToolbar.setHomeButtonEnabled(enable); 388 } 389 390 @Override 391 public void setTitle(int resId) { 392 setTitle(mContext.getString(resId)); 393 } 394 395 @Override 396 public void setSubtitle(int resId) { 397 setSubtitle(mContext.getString(resId)); 398 } 399 400 public void setSelectedNavigationItem(int position) { 401 switch (mDecorToolbar.getNavigationMode()) { 402 case NAVIGATION_MODE_TABS: 403 selectTab(mTabs.get(position)); 404 break; 405 case NAVIGATION_MODE_LIST: 406 mDecorToolbar.setDropdownSelectedPosition(position); 407 break; 408 default: 409 throw new IllegalStateException( 410 "setSelectedNavigationIndex not valid for current navigation mode"); 411 } 412 } 413 414 public void removeAllTabs() { 415 cleanupTabs(); 416 } 417 418 private void cleanupTabs() { 419 if (mSelectedTab != null) { 420 selectTab(null); 421 } 422 mTabs.clear(); 423 if (mTabScrollView != null) { 424 mTabScrollView.removeAllTabs(); 425 } 426 mSavedTabPosition = INVALID_POSITION; 427 } 428 429 public void setTitle(CharSequence title) { 430 mDecorToolbar.setTitle(title); 431 } 432 433 @Override 434 public void setWindowTitle(CharSequence title) { 435 mDecorToolbar.setWindowTitle(title); 436 } 437 438 @Override 439 public boolean requestFocus() { 440 final ViewGroup viewGroup = mDecorToolbar.getViewGroup(); 441 if (viewGroup != null && !viewGroup.hasFocus()) { 442 viewGroup.requestFocus(); 443 return true; 444 } 445 return false; 446 } 447 448 public void setSubtitle(CharSequence subtitle) { 449 mDecorToolbar.setSubtitle(subtitle); 450 } 451 452 public void setDisplayOptions(int options) { 453 if ((options & DISPLAY_HOME_AS_UP) != 0) { 454 mDisplayHomeAsUpSet = true; 455 } 456 mDecorToolbar.setDisplayOptions(options); 457 } 458 459 public void setDisplayOptions(int options, int mask) { 460 final int current = mDecorToolbar.getDisplayOptions(); 461 if ((mask & DISPLAY_HOME_AS_UP) != 0) { 462 mDisplayHomeAsUpSet = true; 463 } 464 mDecorToolbar.setDisplayOptions((options & mask) | (current & ~mask)); 465 } 466 467 public void setBackgroundDrawable(Drawable d) { 468 mContainerView.setPrimaryBackground(d); 469 } 470 471 public void setStackedBackgroundDrawable(Drawable d) { 472 mContainerView.setStackedBackground(d); 473 } 474 475 public void setSplitBackgroundDrawable(Drawable d) { 476 // no-op. We don't support split action bars 477 } 478 479 public View getCustomView() { 480 return mDecorToolbar.getCustomView(); 481 } 482 483 public CharSequence getTitle() { 484 return mDecorToolbar.getTitle(); 485 } 486 487 public CharSequence getSubtitle() { 488 return mDecorToolbar.getSubtitle(); 489 } 490 491 public int getNavigationMode() { 492 return mDecorToolbar.getNavigationMode(); 493 } 494 495 public int getDisplayOptions() { 496 return mDecorToolbar.getDisplayOptions(); 497 } 498 499 public ActionMode startActionMode(ActionMode.Callback callback) { 500 if (mActionMode != null) { 501 mActionMode.finish(); 502 } 503 504 mOverlayLayout.setHideOnContentScrollEnabled(false); 505 mContextView.killMode(); 506 ActionModeImpl mode = new ActionModeImpl(mContextView.getContext(), callback); 507 if (mode.dispatchOnCreate()) { 508 // This needs to be set before invalidate() so that it calls 509 // onPrepareActionMode() 510 mActionMode = mode; 511 mode.invalidate(); 512 mContextView.initForMode(mode); 513 animateToMode(true); 514 mContextView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 515 return mode; 516 } 517 return null; 518 } 519 520 private void configureTab(Tab tab, int position) { 521 final TabImpl tabi = (TabImpl) tab; 522 final ActionBar.TabListener callback = tabi.getCallback(); 523 524 if (callback == null) { 525 throw new IllegalStateException("Action Bar Tab must have a Callback"); 526 } 527 528 tabi.setPosition(position); 529 mTabs.add(position, tabi); 530 531 final int count = mTabs.size(); 532 for (int i = position + 1; i < count; i++) { 533 mTabs.get(i).setPosition(i); 534 } 535 } 536 537 @Override 538 public void addTab(Tab tab) { 539 addTab(tab, mTabs.isEmpty()); 540 } 541 542 @Override 543 public void addTab(Tab tab, int position) { 544 addTab(tab, position, mTabs.isEmpty()); 545 } 546 547 @Override 548 public void addTab(Tab tab, boolean setSelected) { 549 ensureTabsExist(); 550 mTabScrollView.addTab(tab, setSelected); 551 configureTab(tab, mTabs.size()); 552 if (setSelected) { 553 selectTab(tab); 554 } 555 } 556 557 @Override 558 public void addTab(Tab tab, int position, boolean setSelected) { 559 ensureTabsExist(); 560 mTabScrollView.addTab(tab, position, setSelected); 561 configureTab(tab, position); 562 if (setSelected) { 563 selectTab(tab); 564 } 565 } 566 567 @Override 568 public Tab newTab() { 569 return new TabImpl(); 570 } 571 572 @Override 573 public void removeTab(Tab tab) { 574 removeTabAt(tab.getPosition()); 575 } 576 577 @Override 578 public void removeTabAt(int position) { 579 if (mTabScrollView == null) { 580 // No tabs around to remove 581 return; 582 } 583 584 int selectedTabPosition = mSelectedTab != null 585 ? mSelectedTab.getPosition() : mSavedTabPosition; 586 mTabScrollView.removeTabAt(position); 587 TabImpl removedTab = mTabs.remove(position); 588 if (removedTab != null) { 589 removedTab.setPosition(-1); 590 } 591 592 final int newTabCount = mTabs.size(); 593 for (int i = position; i < newTabCount; i++) { 594 mTabs.get(i).setPosition(i); 595 } 596 597 if (selectedTabPosition == position) { 598 selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1))); 599 } 600 } 601 602 @Override 603 public void selectTab(Tab tab) { 604 if (getNavigationMode() != NAVIGATION_MODE_TABS) { 605 mSavedTabPosition = tab != null ? tab.getPosition() : INVALID_POSITION; 606 return; 607 } 608 609 final FragmentTransaction trans; 610 if (mActivity instanceof FragmentActivity && !mDecorToolbar.getViewGroup().isInEditMode()) { 611 // If we're not in edit mode and our Activity is a FragmentActivity, start a tx 612 trans = ((FragmentActivity) mActivity).getSupportFragmentManager() 613 .beginTransaction().disallowAddToBackStack(); 614 } else { 615 trans = null; 616 } 617 618 if (mSelectedTab == tab) { 619 if (mSelectedTab != null) { 620 mSelectedTab.getCallback().onTabReselected(mSelectedTab, trans); 621 mTabScrollView.animateToTab(tab.getPosition()); 622 } 623 } else { 624 mTabScrollView.setTabSelected(tab != null ? tab.getPosition() : Tab.INVALID_POSITION); 625 if (mSelectedTab != null) { 626 mSelectedTab.getCallback().onTabUnselected(mSelectedTab, trans); 627 } 628 mSelectedTab = (TabImpl) tab; 629 if (mSelectedTab != null) { 630 mSelectedTab.getCallback().onTabSelected(mSelectedTab, trans); 631 } 632 } 633 634 if (trans != null && !trans.isEmpty()) { 635 trans.commit(); 636 } 637 } 638 639 @Override 640 public Tab getSelectedTab() { 641 return mSelectedTab; 642 } 643 644 @Override 645 public int getHeight() { 646 return mContainerView.getHeight(); 647 } 648 649 public void enableContentAnimations(boolean enabled) { 650 mContentAnimations = enabled; 651 } 652 653 @Override 654 public void show() { 655 if (mHiddenByApp) { 656 mHiddenByApp = false; 657 updateVisibility(false); 658 } 659 } 660 661 private void showForActionMode() { 662 if (!mShowingForMode) { 663 mShowingForMode = true; 664 if (mOverlayLayout != null) { 665 mOverlayLayout.setShowingForActionMode(true); 666 } 667 updateVisibility(false); 668 } 669 } 670 671 public void showForSystem() { 672 if (mHiddenBySystem) { 673 mHiddenBySystem = false; 674 updateVisibility(true); 675 } 676 } 677 678 @Override 679 public void hide() { 680 if (!mHiddenByApp) { 681 mHiddenByApp = true; 682 updateVisibility(false); 683 } 684 } 685 686 private void hideForActionMode() { 687 if (mShowingForMode) { 688 mShowingForMode = false; 689 if (mOverlayLayout != null) { 690 mOverlayLayout.setShowingForActionMode(false); 691 } 692 updateVisibility(false); 693 } 694 } 695 696 public void hideForSystem() { 697 if (!mHiddenBySystem) { 698 mHiddenBySystem = true; 699 updateVisibility(true); 700 } 701 } 702 703 @Override 704 public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) { 705 if (hideOnContentScroll && !mOverlayLayout.isInOverlayMode()) { 706 throw new IllegalStateException("Action bar must be in overlay mode " + 707 "(Window.FEATURE_OVERLAY_ACTION_BAR) to enable hide on content scroll"); 708 } 709 mHideOnContentScroll = hideOnContentScroll; 710 mOverlayLayout.setHideOnContentScrollEnabled(hideOnContentScroll); 711 } 712 713 @Override 714 public boolean isHideOnContentScrollEnabled() { 715 return mOverlayLayout.isHideOnContentScrollEnabled(); 716 } 717 718 @Override 719 public int getHideOffset() { 720 return mOverlayLayout.getActionBarHideOffset(); 721 } 722 723 @Override 724 public void setHideOffset(int offset) { 725 if (offset != 0 && !mOverlayLayout.isInOverlayMode()) { 726 throw new IllegalStateException("Action bar must be in overlay mode " + 727 "(Window.FEATURE_OVERLAY_ACTION_BAR) to set a non-zero hide offset"); 728 } 729 mOverlayLayout.setActionBarHideOffset(offset); 730 } 731 732 private static boolean checkShowingFlags(boolean hiddenByApp, boolean hiddenBySystem, 733 boolean showingForMode) { 734 if (showingForMode) { 735 return true; 736 } else if (hiddenByApp || hiddenBySystem) { 737 return false; 738 } else { 739 return true; 740 } 741 } 742 743 private void updateVisibility(boolean fromSystem) { 744 // Based on the current state, should we be hidden or shown? 745 final boolean shown = checkShowingFlags(mHiddenByApp, mHiddenBySystem, 746 mShowingForMode); 747 748 if (shown) { 749 if (!mNowShowing) { 750 mNowShowing = true; 751 doShow(fromSystem); 752 } 753 } else { 754 if (mNowShowing) { 755 mNowShowing = false; 756 doHide(fromSystem); 757 } 758 } 759 } 760 761 public void doShow(boolean fromSystem) { 762 if (mCurrentShowAnim != null) { 763 mCurrentShowAnim.cancel(); 764 } 765 mContainerView.setVisibility(View.VISIBLE); 766 767 if (mCurWindowVisibility == View.VISIBLE && ALLOW_SHOW_HIDE_ANIMATIONS && 768 (mShowHideAnimationEnabled || fromSystem)) { 769 // because we're about to ask its window loc 770 ViewCompat.setTranslationY(mContainerView, 0f); 771 float startingY = -mContainerView.getHeight(); 772 if (fromSystem) { 773 int topLeft[] = {0, 0}; 774 mContainerView.getLocationInWindow(topLeft); 775 startingY -= topLeft[1]; 776 } 777 ViewCompat.setTranslationY(mContainerView, startingY); 778 ViewPropertyAnimatorCompatSet anim = new ViewPropertyAnimatorCompatSet(); 779 ViewPropertyAnimatorCompat a = ViewCompat.animate(mContainerView).translationY(0f); 780 a.setUpdateListener(mUpdateListener); 781 anim.play(a); 782 if (mContentAnimations && mContentView != null) { 783 ViewCompat.setTranslationY(mContentView, startingY); 784 anim.play(ViewCompat.animate(mContentView).translationY(0f)); 785 } 786 anim.setInterpolator(sShowInterpolator); 787 anim.setDuration(250); 788 // If this is being shown from the system, add a small delay. 789 // This is because we will also be animating in the status bar, 790 // and these two elements can't be done in lock-step. So we give 791 // a little time for the status bar to start its animation before 792 // the action bar animates. (This corresponds to the corresponding 793 // case when hiding, where the status bar has a small delay before 794 // starting.) 795 anim.setListener(mShowListener); 796 mCurrentShowAnim = anim; 797 anim.start(); 798 } else { 799 ViewCompat.setAlpha(mContainerView, 1f); 800 ViewCompat.setTranslationY(mContainerView, 0); 801 if (mContentAnimations && mContentView != null) { 802 ViewCompat.setTranslationY(mContentView, 0); 803 } 804 mShowListener.onAnimationEnd(null); 805 } 806 if (mOverlayLayout != null) { 807 ViewCompat.requestApplyInsets(mOverlayLayout); 808 } 809 } 810 811 public void doHide(boolean fromSystem) { 812 if (mCurrentShowAnim != null) { 813 mCurrentShowAnim.cancel(); 814 } 815 816 if (mCurWindowVisibility == View.VISIBLE && ALLOW_SHOW_HIDE_ANIMATIONS && 817 (mShowHideAnimationEnabled || fromSystem)) { 818 ViewCompat.setAlpha(mContainerView, 1f); 819 mContainerView.setTransitioning(true); 820 ViewPropertyAnimatorCompatSet anim = new ViewPropertyAnimatorCompatSet(); 821 float endingY = -mContainerView.getHeight(); 822 if (fromSystem) { 823 int topLeft[] = {0, 0}; 824 mContainerView.getLocationInWindow(topLeft); 825 endingY -= topLeft[1]; 826 } 827 ViewPropertyAnimatorCompat a = ViewCompat.animate(mContainerView).translationY(endingY); 828 a.setUpdateListener(mUpdateListener); 829 anim.play(a); 830 if (mContentAnimations && mContentView != null) { 831 anim.play(ViewCompat.animate(mContentView).translationY(endingY)); 832 } 833 anim.setInterpolator(sHideInterpolator); 834 anim.setDuration(250); 835 anim.setListener(mHideListener); 836 mCurrentShowAnim = anim; 837 anim.start(); 838 } else { 839 mHideListener.onAnimationEnd(null); 840 } 841 } 842 843 public boolean isShowing() { 844 final int height = getHeight(); 845 // Take into account the case where the bar has a 0 height due to not being measured yet. 846 return mNowShowing && (height == 0 || getHideOffset() < height); 847 } 848 849 public void animateToMode(boolean toActionMode) { 850 if (toActionMode) { 851 showForActionMode(); 852 } else { 853 hideForActionMode(); 854 } 855 856 if (shouldAnimateContextView()) { 857 ViewPropertyAnimatorCompat fadeIn, fadeOut; 858 if (toActionMode) { 859 // We use INVISIBLE for the Toolbar to make sure that the container has a non-zero 860 // height throughout. The context view is GONE initially, so will not have been laid 861 // out when the animation starts. This can lead to the container collapsing to 0px 862 // height for a short period. 863 fadeOut = mDecorToolbar.setupAnimatorToVisibility(View.INVISIBLE, 864 FADE_OUT_DURATION_MS); 865 fadeIn = mContextView.setupAnimatorToVisibility(View.VISIBLE, 866 FADE_IN_DURATION_MS); 867 } else { 868 fadeIn = mDecorToolbar.setupAnimatorToVisibility(View.VISIBLE, 869 FADE_IN_DURATION_MS); 870 fadeOut = mContextView.setupAnimatorToVisibility(View.GONE, 871 FADE_OUT_DURATION_MS); 872 } 873 ViewPropertyAnimatorCompatSet set = new ViewPropertyAnimatorCompatSet(); 874 set.playSequentially(fadeOut, fadeIn); 875 set.start(); 876 } else { 877 if (toActionMode) { 878 mDecorToolbar.setVisibility(View.INVISIBLE); 879 mContextView.setVisibility(View.VISIBLE); 880 } else { 881 mDecorToolbar.setVisibility(View.VISIBLE); 882 mContextView.setVisibility(View.GONE); 883 } 884 } 885 // mTabScrollView's visibility is not affected by action mode. 886 } 887 888 private boolean shouldAnimateContextView() { 889 // We only to animate the action mode in if the container view has already been laid out. 890 // If it hasn't been laid out, it hasn't been drawn to screen yet. 891 return ViewCompat.isLaidOut(mContainerView); 892 } 893 894 public Context getThemedContext() { 895 if (mThemedContext == null) { 896 TypedValue outValue = new TypedValue(); 897 Resources.Theme currentTheme = mContext.getTheme(); 898 currentTheme.resolveAttribute(R.attr.actionBarWidgetTheme, outValue, true); 899 final int targetThemeRes = outValue.resourceId; 900 901 if (targetThemeRes != 0) { 902 mThemedContext = new ContextThemeWrapper(mContext, targetThemeRes); 903 } else { 904 mThemedContext = mContext; 905 } 906 } 907 return mThemedContext; 908 } 909 910 @Override 911 public boolean isTitleTruncated() { 912 return mDecorToolbar != null && mDecorToolbar.isTitleTruncated(); 913 } 914 915 @Override 916 public void setHomeAsUpIndicator(Drawable indicator) { 917 mDecorToolbar.setNavigationIcon(indicator); 918 } 919 920 @Override 921 public void setHomeAsUpIndicator(int resId) { 922 mDecorToolbar.setNavigationIcon(resId); 923 } 924 925 @Override 926 public void setHomeActionContentDescription(CharSequence description) { 927 mDecorToolbar.setNavigationContentDescription(description); 928 } 929 930 @Override 931 public void setHomeActionContentDescription(int resId) { 932 mDecorToolbar.setNavigationContentDescription(resId); 933 } 934 935 @Override 936 public void onContentScrollStarted() { 937 if (mCurrentShowAnim != null) { 938 mCurrentShowAnim.cancel(); 939 mCurrentShowAnim = null; 940 } 941 } 942 943 @Override 944 public void onContentScrollStopped() { 945 } 946 947 @Override 948 public boolean collapseActionView() { 949 if (mDecorToolbar != null && mDecorToolbar.hasExpandedActionView()) { 950 mDecorToolbar.collapseActionView(); 951 return true; 952 } 953 return false; 954 } 955 956 /** 957 * @hide 958 */ 959 public class ActionModeImpl extends ActionMode implements MenuBuilder.Callback { 960 private final Context mActionModeContext; 961 private final MenuBuilder mMenu; 962 963 private ActionMode.Callback mCallback; 964 private WeakReference<View> mCustomView; 965 966 public ActionModeImpl(Context context, ActionMode.Callback callback) { 967 mActionModeContext = context; 968 mCallback = callback; 969 mMenu = new MenuBuilder(context) 970 .setDefaultShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 971 mMenu.setCallback(this); 972 } 973 974 @Override 975 public MenuInflater getMenuInflater() { 976 return new SupportMenuInflater(mActionModeContext); 977 } 978 979 @Override 980 public Menu getMenu() { 981 return mMenu; 982 } 983 984 @Override 985 public void finish() { 986 if (mActionMode != this) { 987 // Not the active action mode - no-op 988 return; 989 } 990 991 // If this change in state is going to cause the action bar 992 // to be hidden, defer the onDestroy callback until the animation 993 // is finished and associated relayout is about to happen. This lets 994 // apps better anticipate visibility and layout behavior. 995 if (!checkShowingFlags(mHiddenByApp, mHiddenBySystem, false)) { 996 // With the current state but the action bar hidden, our 997 // overall showing state is going to be false. 998 mDeferredDestroyActionMode = this; 999 mDeferredModeDestroyCallback = mCallback; 1000 } else { 1001 mCallback.onDestroyActionMode(this); 1002 } 1003 mCallback = null; 1004 animateToMode(false); 1005 1006 // Clear out the context mode views after the animation finishes 1007 mContextView.closeMode(); 1008 mDecorToolbar.getViewGroup().sendAccessibilityEvent( 1009 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 1010 mOverlayLayout.setHideOnContentScrollEnabled(mHideOnContentScroll); 1011 1012 mActionMode = null; 1013 } 1014 1015 @Override 1016 public void invalidate() { 1017 if (mActionMode != this) { 1018 // Not the active action mode - no-op. It's possible we are 1019 // currently deferring onDestroy, so the app doesn't yet know we 1020 // are going away and is trying to use us. That's also a no-op. 1021 return; 1022 } 1023 1024 mMenu.stopDispatchingItemsChanged(); 1025 try { 1026 mCallback.onPrepareActionMode(this, mMenu); 1027 } finally { 1028 mMenu.startDispatchingItemsChanged(); 1029 } 1030 } 1031 1032 public boolean dispatchOnCreate() { 1033 mMenu.stopDispatchingItemsChanged(); 1034 try { 1035 return mCallback.onCreateActionMode(this, mMenu); 1036 } finally { 1037 mMenu.startDispatchingItemsChanged(); 1038 } 1039 } 1040 1041 @Override 1042 public void setCustomView(View view) { 1043 mContextView.setCustomView(view); 1044 mCustomView = new WeakReference<View>(view); 1045 } 1046 1047 @Override 1048 public void setSubtitle(CharSequence subtitle) { 1049 mContextView.setSubtitle(subtitle); 1050 } 1051 1052 @Override 1053 public void setTitle(CharSequence title) { 1054 mContextView.setTitle(title); 1055 } 1056 1057 @Override 1058 public void setTitle(int resId) { 1059 setTitle(mContext.getResources().getString(resId)); 1060 } 1061 1062 @Override 1063 public void setSubtitle(int resId) { 1064 setSubtitle(mContext.getResources().getString(resId)); 1065 } 1066 1067 @Override 1068 public CharSequence getTitle() { 1069 return mContextView.getTitle(); 1070 } 1071 1072 @Override 1073 public CharSequence getSubtitle() { 1074 return mContextView.getSubtitle(); 1075 } 1076 1077 @Override 1078 public void setTitleOptionalHint(boolean titleOptional) { 1079 super.setTitleOptionalHint(titleOptional); 1080 mContextView.setTitleOptional(titleOptional); 1081 } 1082 1083 @Override 1084 public boolean isTitleOptional() { 1085 return mContextView.isTitleOptional(); 1086 } 1087 1088 @Override 1089 public View getCustomView() { 1090 return mCustomView != null ? mCustomView.get() : null; 1091 } 1092 1093 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) { 1094 if (mCallback != null) { 1095 return mCallback.onActionItemClicked(this, item); 1096 } else { 1097 return false; 1098 } 1099 } 1100 1101 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 1102 } 1103 1104 public boolean onSubMenuSelected(SubMenuBuilder subMenu) { 1105 if (mCallback == null) { 1106 return false; 1107 } 1108 1109 if (!subMenu.hasVisibleItems()) { 1110 return true; 1111 } 1112 1113 new MenuPopupHelper(getThemedContext(), subMenu).show(); 1114 return true; 1115 } 1116 1117 public void onCloseSubMenu(SubMenuBuilder menu) { 1118 } 1119 1120 public void onMenuModeChange(MenuBuilder menu) { 1121 if (mCallback == null) { 1122 return; 1123 } 1124 invalidate(); 1125 mContextView.showOverflowMenu(); 1126 } 1127 } 1128 1129 /** 1130 * @hide 1131 */ 1132 public class TabImpl extends ActionBar.Tab { 1133 private ActionBar.TabListener mCallback; 1134 private Object mTag; 1135 private Drawable mIcon; 1136 private CharSequence mText; 1137 private CharSequence mContentDesc; 1138 private int mPosition = -1; 1139 private View mCustomView; 1140 1141 @Override 1142 public Object getTag() { 1143 return mTag; 1144 } 1145 1146 @Override 1147 public Tab setTag(Object tag) { 1148 mTag = tag; 1149 return this; 1150 } 1151 1152 public ActionBar.TabListener getCallback() { 1153 return mCallback; 1154 } 1155 1156 @Override 1157 public Tab setTabListener(ActionBar.TabListener callback) { 1158 mCallback = callback; 1159 return this; 1160 } 1161 1162 @Override 1163 public View getCustomView() { 1164 return mCustomView; 1165 } 1166 1167 @Override 1168 public Tab setCustomView(View view) { 1169 mCustomView = view; 1170 if (mPosition >= 0) { 1171 mTabScrollView.updateTab(mPosition); 1172 } 1173 return this; 1174 } 1175 1176 @Override 1177 public Tab setCustomView(int layoutResId) { 1178 return setCustomView(LayoutInflater.from(getThemedContext()) 1179 .inflate(layoutResId, null)); 1180 } 1181 1182 @Override 1183 public Drawable getIcon() { 1184 return mIcon; 1185 } 1186 1187 @Override 1188 public int getPosition() { 1189 return mPosition; 1190 } 1191 1192 public void setPosition(int position) { 1193 mPosition = position; 1194 } 1195 1196 @Override 1197 public CharSequence getText() { 1198 return mText; 1199 } 1200 1201 @Override 1202 public Tab setIcon(Drawable icon) { 1203 mIcon = icon; 1204 if (mPosition >= 0) { 1205 mTabScrollView.updateTab(mPosition); 1206 } 1207 return this; 1208 } 1209 1210 @Override 1211 public Tab setIcon(int resId) { 1212 return setIcon(AppCompatDrawableManager.get().getDrawable(mContext, resId)); 1213 } 1214 1215 @Override 1216 public Tab setText(CharSequence text) { 1217 mText = text; 1218 if (mPosition >= 0) { 1219 mTabScrollView.updateTab(mPosition); 1220 } 1221 return this; 1222 } 1223 1224 @Override 1225 public Tab setText(int resId) { 1226 return setText(mContext.getResources().getText(resId)); 1227 } 1228 1229 @Override 1230 public void select() { 1231 selectTab(this); 1232 } 1233 1234 @Override 1235 public Tab setContentDescription(int resId) { 1236 return setContentDescription(mContext.getResources().getText(resId)); 1237 } 1238 1239 @Override 1240 public Tab setContentDescription(CharSequence contentDesc) { 1241 mContentDesc = contentDesc; 1242 if (mPosition >= 0) { 1243 mTabScrollView.updateTab(mPosition); 1244 } 1245 return this; 1246 } 1247 1248 @Override 1249 public CharSequence getContentDescription() { 1250 return mContentDesc; 1251 } 1252 } 1253 1254 @Override 1255 public void setCustomView(View view) { 1256 mDecorToolbar.setCustomView(view); 1257 } 1258 1259 @Override 1260 public void setCustomView(View view, LayoutParams layoutParams) { 1261 view.setLayoutParams(layoutParams); 1262 mDecorToolbar.setCustomView(view); 1263 } 1264 1265 @Override 1266 public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) { 1267 mDecorToolbar.setDropdownParams(adapter, new NavItemSelectedListener(callback)); 1268 } 1269 1270 @Override 1271 public int getSelectedNavigationIndex() { 1272 switch (mDecorToolbar.getNavigationMode()) { 1273 case NAVIGATION_MODE_TABS: 1274 return mSelectedTab != null ? mSelectedTab.getPosition() : -1; 1275 case NAVIGATION_MODE_LIST: 1276 return mDecorToolbar.getDropdownSelectedPosition(); 1277 default: 1278 return -1; 1279 } 1280 } 1281 1282 @Override 1283 public int getNavigationItemCount() { 1284 switch (mDecorToolbar.getNavigationMode()) { 1285 case NAVIGATION_MODE_TABS: 1286 return mTabs.size(); 1287 case NAVIGATION_MODE_LIST: 1288 return mDecorToolbar.getDropdownItemCount(); 1289 default: 1290 return 0; 1291 } 1292 } 1293 1294 @Override 1295 public int getTabCount() { 1296 return mTabs.size(); 1297 } 1298 1299 @Override 1300 public void setNavigationMode(int mode) { 1301 final int oldMode = mDecorToolbar.getNavigationMode(); 1302 switch (oldMode) { 1303 case NAVIGATION_MODE_TABS: 1304 mSavedTabPosition = getSelectedNavigationIndex(); 1305 selectTab(null); 1306 mTabScrollView.setVisibility(View.GONE); 1307 break; 1308 } 1309 if (oldMode != mode && !mHasEmbeddedTabs) { 1310 if (mOverlayLayout != null) { 1311 ViewCompat.requestApplyInsets(mOverlayLayout); 1312 } 1313 } 1314 mDecorToolbar.setNavigationMode(mode); 1315 switch (mode) { 1316 case NAVIGATION_MODE_TABS: 1317 ensureTabsExist(); 1318 mTabScrollView.setVisibility(View.VISIBLE); 1319 if (mSavedTabPosition != INVALID_POSITION) { 1320 setSelectedNavigationItem(mSavedTabPosition); 1321 mSavedTabPosition = INVALID_POSITION; 1322 } 1323 break; 1324 } 1325 mDecorToolbar.setCollapsible(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs); 1326 mOverlayLayout.setHasNonEmbeddedTabs(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs); 1327 } 1328 1329 @Override 1330 public Tab getTabAt(int index) { 1331 return mTabs.get(index); 1332 } 1333 1334 1335 @Override 1336 public void setIcon(int resId) { 1337 mDecorToolbar.setIcon(resId); 1338 } 1339 1340 @Override 1341 public void setIcon(Drawable icon) { 1342 mDecorToolbar.setIcon(icon); 1343 } 1344 1345 public boolean hasIcon() { 1346 return mDecorToolbar.hasIcon(); 1347 } 1348 1349 @Override 1350 public void setLogo(int resId) { 1351 mDecorToolbar.setLogo(resId); 1352 } 1353 1354 @Override 1355 public void setLogo(Drawable logo) { 1356 mDecorToolbar.setLogo(logo); 1357 } 1358 1359 public boolean hasLogo() { 1360 return mDecorToolbar.hasLogo(); 1361 } 1362 1363 public void setDefaultDisplayHomeAsUpEnabled(boolean enable) { 1364 if (!mDisplayHomeAsUpSet) { 1365 setDisplayHomeAsUpEnabled(enable); 1366 } 1367 } 1368} 1369