ToolbarWidgetWrapper.java revision acf94897e0924ff5fcd3c5b371ab1aa620cb1dc6
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 17 18package android.support.v7.internal.widget; 19 20import android.app.ActionBar; 21import android.content.Context; 22import android.graphics.drawable.Drawable; 23import android.os.Parcelable; 24import android.support.v4.view.ViewCompat; 25import android.support.v4.view.ViewPropertyAnimatorListenerAdapter; 26import android.support.v7.appcompat.R; 27import android.support.v7.internal.app.WindowCallback; 28import android.support.v7.internal.view.menu.ActionMenuItem; 29import android.support.v7.internal.view.menu.MenuBuilder; 30import android.support.v7.internal.view.menu.MenuPresenter; 31import android.support.v7.widget.ActionMenuPresenter; 32import android.support.v7.widget.Toolbar; 33import android.text.TextUtils; 34import android.util.Log; 35import android.util.SparseArray; 36import android.view.Gravity; 37import android.view.LayoutInflater; 38import android.view.Menu; 39import android.view.View; 40import android.view.ViewGroup; 41import android.view.Window; 42import android.widget.SpinnerAdapter; 43 44/** 45 * Internal class used to interact with the Toolbar widget without 46 * exposing interface methods to the public API. 47 * 48 * <p>ToolbarWidgetWrapper manages the differences between Toolbar and ActionBarView 49 * so that either variant acting as a 50 * {@link android.support.v7.internal.app.WindowDecorActionBar WindowDecorActionBar} can behave 51 * in the same way.</p> 52 * 53 * @hide 54 */ 55public class ToolbarWidgetWrapper implements DecorToolbar { 56 private static final String TAG = "ToolbarWidgetWrapper"; 57 58 private static final int AFFECTS_LOGO_MASK = 59 ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_USE_LOGO; 60 61 private Toolbar mToolbar; 62 63 private int mDisplayOpts; 64 private View mTabView; 65 private SpinnerCompat mSpinner; 66 private View mCustomView; 67 68 private Drawable mIcon; 69 private Drawable mLogo; 70 private Drawable mNavIcon; 71 72 private boolean mTitleSet; 73 private CharSequence mTitle; 74 private CharSequence mSubtitle; 75 private CharSequence mHomeDescription; 76 77 private WindowCallback mWindowCallback; 78 private boolean mMenuPrepared; 79 private ActionMenuPresenter mActionMenuPresenter; 80 81 private int mNavigationMode = ActionBar.NAVIGATION_MODE_STANDARD; 82 83 private final TintManager mTintManager; 84 private int mDefaultNavigationContentDescription = 0; 85 private Drawable mDefaultNavigationIcon; 86 87 public ToolbarWidgetWrapper(Toolbar toolbar, boolean style) { 88 this(toolbar, style, R.string.abc_action_bar_up_description, 89 R.drawable.abc_ic_ab_back_mtrl_am_alpha); 90 } 91 92 public ToolbarWidgetWrapper(Toolbar toolbar, boolean style, 93 int defaultNavigationContentDescription, int defaultNavigationIcon) { 94 mToolbar = toolbar; 95 mTitle = toolbar.getTitle(); 96 mSubtitle = toolbar.getSubtitle(); 97 mTitleSet = mTitle != null; 98 99 if (style) { 100 final TintTypedArray a = TintTypedArray.obtainStyledAttributes(toolbar.getContext(), 101 null, R.styleable.ActionBar, R.attr.actionBarStyle, 0); 102 103 final CharSequence title = a.getText(R.styleable.ActionBar_title); 104 if (!TextUtils.isEmpty(title)) { 105 setTitle(title); 106 } 107 108 final CharSequence subtitle = a.getText(R.styleable.ActionBar_subtitle); 109 if (!TextUtils.isEmpty(subtitle)) { 110 setSubtitle(subtitle); 111 } 112 113 final Drawable logo = a.getDrawable(R.styleable.ActionBar_logo); 114 if (logo != null) { 115 setLogo(logo); 116 } 117 118 final Drawable icon = a.getDrawable(R.styleable.ActionBar_icon); 119 if (icon != null) { 120 setIcon(icon); 121 } 122 123 final Drawable navIcon = a.getDrawable(R.styleable.ActionBar_homeAsUpIndicator); 124 if (navIcon != null) { 125 setNavigationIcon(navIcon); 126 } 127 128 setDisplayOptions(a.getInt(R.styleable.ActionBar_displayOptions, 0)); 129 130 final int customNavId = a.getResourceId( 131 R.styleable.ActionBar_customNavigationLayout, 0); 132 if (customNavId != 0) { 133 setCustomView(LayoutInflater.from(mToolbar.getContext()).inflate(customNavId, 134 mToolbar, false)); 135 setDisplayOptions(mDisplayOpts | ActionBar.DISPLAY_SHOW_CUSTOM); 136 } 137 138 final int height = a.getLayoutDimension(R.styleable.ActionBar_height, 0); 139 if (height > 0) { 140 final ViewGroup.LayoutParams lp = mToolbar.getLayoutParams(); 141 lp.height = height; 142 mToolbar.setLayoutParams(lp); 143 } 144 145 final int contentInsetStart = a.getDimensionPixelOffset( 146 R.styleable.ActionBar_contentInsetStart, -1); 147 final int contentInsetEnd = a.getDimensionPixelOffset( 148 R.styleable.ActionBar_contentInsetEnd, -1); 149 if (contentInsetStart >= 0 || contentInsetEnd >= 0) { 150 mToolbar.setContentInsetsRelative(Math.max(contentInsetStart, 0), 151 Math.max(contentInsetEnd, 0)); 152 } 153 154 final int titleTextStyle = a.getResourceId(R.styleable.ActionBar_titleTextStyle, 0); 155 if (titleTextStyle != 0) { 156 mToolbar.setTitleTextAppearance(mToolbar.getContext(), titleTextStyle); 157 } 158 159 final int subtitleTextStyle = a.getResourceId( 160 R.styleable.ActionBar_subtitleTextStyle, 0); 161 if (subtitleTextStyle != 0) { 162 mToolbar.setSubtitleTextAppearance(mToolbar.getContext(), subtitleTextStyle); 163 } 164 165 final int popupTheme = a.getResourceId(R.styleable.ActionBar_popupTheme, 0); 166 if (popupTheme != 0) { 167 mToolbar.setPopupTheme(popupTheme); 168 } 169 170 a.recycle(); 171 // Keep the TintManager in case we need it later 172 mTintManager = a.getTintManager(); 173 } else { 174 mDisplayOpts = detectDisplayOptions(); 175 // Create a TintManager in case we need it later 176 mTintManager = new TintManager(toolbar.getContext()); 177 } 178 179 setDefaultNavigationContentDescription(defaultNavigationContentDescription); 180 mHomeDescription = mToolbar.getNavigationContentDescription(); 181 182 setDefaultNavigationIcon(mTintManager.getDrawable(defaultNavigationIcon)); 183 184 mToolbar.setNavigationOnClickListener(new View.OnClickListener() { 185 final ActionMenuItem mNavItem = new ActionMenuItem(mToolbar.getContext(), 186 0, android.R.id.home, 0, 0, mTitle); 187 @Override 188 public void onClick(View v) { 189 if (mWindowCallback != null && mMenuPrepared) { 190 mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mNavItem); 191 } 192 } 193 }); 194 } 195 196 /** 197 * Sets the default content description for the navigation button. 198 * <p> 199 * It changes the current content description if and only if the provided resource id is 200 * different than the current default resource id and the current content description is empty. 201 * 202 * @param defaultNavigationContentDescription The resource id for the default content 203 * description 204 */ 205 @Override 206 public void setDefaultNavigationContentDescription(int defaultNavigationContentDescription) { 207 if (defaultNavigationContentDescription == mDefaultNavigationContentDescription) { 208 return; 209 } 210 mDefaultNavigationContentDescription = defaultNavigationContentDescription; 211 if (TextUtils.isEmpty(mToolbar.getNavigationContentDescription())) { 212 setNavigationContentDescription(mDefaultNavigationContentDescription); 213 } 214 } 215 216 @Override 217 public void setDefaultNavigationIcon(Drawable defaultNavigationIcon) { 218 if (mDefaultNavigationIcon != defaultNavigationIcon) { 219 mDefaultNavigationIcon = defaultNavigationIcon; 220 updateNavigationIcon(); 221 } 222 } 223 224 private int detectDisplayOptions() { 225 int opts = ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_HOME | 226 ActionBar.DISPLAY_USE_LOGO; 227 if (mToolbar.getNavigationIcon() != null) { 228 opts |= ActionBar.DISPLAY_HOME_AS_UP; 229 } 230 return opts; 231 } 232 233 @Override 234 public ViewGroup getViewGroup() { 235 return mToolbar; 236 } 237 238 @Override 239 public Context getContext() { 240 return mToolbar.getContext(); 241 } 242 243 @Override 244 public boolean isSplit() { 245 return false; 246 } 247 248 @Override 249 public boolean hasExpandedActionView() { 250 return mToolbar.hasExpandedActionView(); 251 } 252 253 @Override 254 public void collapseActionView() { 255 mToolbar.collapseActionView(); 256 } 257 258 @Override 259 public void setWindowCallback(WindowCallback cb) { 260 mWindowCallback = cb; 261 } 262 263 @Override 264 public void setWindowTitle(CharSequence title) { 265 // "Real" title always trumps window title. 266 if (!mTitleSet) { 267 setTitleInt(title); 268 } 269 } 270 271 @Override 272 public CharSequence getTitle() { 273 return mToolbar.getTitle(); 274 } 275 276 @Override 277 public void setTitle(CharSequence title) { 278 mTitleSet = true; 279 setTitleInt(title); 280 } 281 282 private void setTitleInt(CharSequence title) { 283 mTitle = title; 284 if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) { 285 mToolbar.setTitle(title); 286 } 287 } 288 289 @Override 290 public CharSequence getSubtitle() { 291 return mToolbar.getSubtitle(); 292 } 293 294 @Override 295 public void setSubtitle(CharSequence subtitle) { 296 mSubtitle = subtitle; 297 if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) { 298 mToolbar.setSubtitle(subtitle); 299 } 300 } 301 302 @Override 303 public void initProgress() { 304 Log.i(TAG, "Progress display unsupported"); 305 } 306 307 @Override 308 public void initIndeterminateProgress() { 309 Log.i(TAG, "Progress display unsupported"); 310 } 311 312 @Override 313 public boolean canSplit() { 314 return false; 315 } 316 317 @Override 318 public void setSplitView(ViewGroup splitView) { 319 } 320 321 @Override 322 public void setSplitToolbar(boolean split) { 323 if (split) { 324 throw new UnsupportedOperationException("Cannot split an android.widget.Toolbar"); 325 } 326 } 327 328 @Override 329 public void setSplitWhenNarrow(boolean splitWhenNarrow) { 330 // Ignore. 331 } 332 333 @Override 334 public boolean hasIcon() { 335 return mIcon != null; 336 } 337 338 @Override 339 public boolean hasLogo() { 340 return mLogo != null; 341 } 342 343 @Override 344 public void setIcon(int resId) { 345 setIcon(resId != 0 ? mTintManager.getDrawable(resId) : null); 346 } 347 348 @Override 349 public void setIcon(Drawable d) { 350 mIcon = d; 351 updateToolbarLogo(); 352 } 353 354 @Override 355 public void setLogo(int resId) { 356 setLogo(resId != 0 ? mTintManager.getDrawable(resId) : null); 357 } 358 359 @Override 360 public void setLogo(Drawable d) { 361 mLogo = d; 362 updateToolbarLogo(); 363 } 364 365 private void updateToolbarLogo() { 366 Drawable logo = null; 367 if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_HOME) != 0) { 368 if ((mDisplayOpts & ActionBar.DISPLAY_USE_LOGO) != 0) { 369 logo = mLogo != null ? mLogo : mIcon; 370 } else { 371 logo = mIcon; 372 } 373 } 374 mToolbar.setLogo(logo); 375 } 376 377 @Override 378 public boolean canShowOverflowMenu() { 379 return mToolbar.canShowOverflowMenu(); 380 } 381 382 @Override 383 public boolean isOverflowMenuShowing() { 384 return mToolbar.isOverflowMenuShowing(); 385 } 386 387 @Override 388 public boolean isOverflowMenuShowPending() { 389 return mToolbar.isOverflowMenuShowPending(); 390 } 391 392 @Override 393 public boolean showOverflowMenu() { 394 return mToolbar.showOverflowMenu(); 395 } 396 397 @Override 398 public boolean hideOverflowMenu() { 399 return mToolbar.hideOverflowMenu(); 400 } 401 402 @Override 403 public void setMenuPrepared() { 404 mMenuPrepared = true; 405 } 406 407 @Override 408 public void setMenu(Menu menu, MenuPresenter.Callback cb) { 409 if (mActionMenuPresenter == null) { 410 mActionMenuPresenter = new ActionMenuPresenter(mToolbar.getContext()); 411 mActionMenuPresenter.setId(R.id.action_menu_presenter); 412 } 413 mActionMenuPresenter.setCallback(cb); 414 mToolbar.setMenu((MenuBuilder) menu, mActionMenuPresenter); 415 } 416 417 @Override 418 public void dismissPopupMenus() { 419 mToolbar.dismissPopupMenus(); 420 } 421 422 @Override 423 public int getDisplayOptions() { 424 return mDisplayOpts; 425 } 426 427 @Override 428 public void setDisplayOptions(int newOpts) { 429 final int oldOpts = mDisplayOpts; 430 final int changed = oldOpts ^ newOpts; 431 mDisplayOpts = newOpts; 432 if (changed != 0) { 433 if ((changed & ActionBar.DISPLAY_HOME_AS_UP) != 0) { 434 if ((newOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) { 435 updateNavigationIcon(); 436 updateHomeAccessibility(); 437 } else { 438 mToolbar.setNavigationIcon(null); 439 } 440 } 441 442 if ((changed & AFFECTS_LOGO_MASK) != 0) { 443 updateToolbarLogo(); 444 } 445 446 if ((changed & ActionBar.DISPLAY_SHOW_TITLE) != 0) { 447 if ((newOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) { 448 mToolbar.setTitle(mTitle); 449 mToolbar.setSubtitle(mSubtitle); 450 } else { 451 mToolbar.setTitle(null); 452 mToolbar.setSubtitle(null); 453 } 454 } 455 456 if ((changed & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomView != null) { 457 if ((newOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) { 458 mToolbar.addView(mCustomView); 459 } else { 460 mToolbar.removeView(mCustomView); 461 } 462 } 463 } 464 } 465 466 @Override 467 public void setEmbeddedTabView(ScrollingTabContainerView tabView) { 468 if (mTabView != null && mTabView.getParent() == mToolbar) { 469 mToolbar.removeView(mTabView); 470 } 471 mTabView = tabView; 472 if (tabView != null && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) { 473 mToolbar.addView(mTabView, 0); 474 Toolbar.LayoutParams lp = (Toolbar.LayoutParams) mTabView.getLayoutParams(); 475 lp.width = ViewGroup.LayoutParams.WRAP_CONTENT; 476 lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; 477 lp.gravity = Gravity.START | Gravity.BOTTOM; 478 tabView.setAllowCollapse(true); 479 } 480 } 481 482 @Override 483 public boolean hasEmbeddedTabs() { 484 return mTabView != null; 485 } 486 487 @Override 488 public boolean isTitleTruncated() { 489 return mToolbar.isTitleTruncated(); 490 } 491 492 @Override 493 public void setCollapsible(boolean collapsible) { 494 mToolbar.setCollapsible(collapsible); 495 } 496 497 @Override 498 public void setHomeButtonEnabled(boolean enable) { 499 // Ignore 500 } 501 502 @Override 503 public int getNavigationMode() { 504 return mNavigationMode; 505 } 506 507 @Override 508 public void setNavigationMode(int mode) { 509 final int oldMode = mNavigationMode; 510 if (mode != oldMode) { 511 switch (oldMode) { 512 case ActionBar.NAVIGATION_MODE_LIST: 513 if (mSpinner != null && mSpinner.getParent() == mToolbar) { 514 mToolbar.removeView(mSpinner); 515 } 516 break; 517 case ActionBar.NAVIGATION_MODE_TABS: 518 if (mTabView != null && mTabView.getParent() == mToolbar) { 519 mToolbar.removeView(mTabView); 520 } 521 break; 522 } 523 524 mNavigationMode = mode; 525 526 switch (mode) { 527 case ActionBar.NAVIGATION_MODE_STANDARD: 528 break; 529 case ActionBar.NAVIGATION_MODE_LIST: 530 ensureSpinner(); 531 mToolbar.addView(mSpinner, 0); 532 break; 533 case ActionBar.NAVIGATION_MODE_TABS: 534 if (mTabView != null) { 535 mToolbar.addView(mTabView, 0); 536 Toolbar.LayoutParams lp = (Toolbar.LayoutParams) mTabView.getLayoutParams(); 537 lp.width = ViewGroup.LayoutParams.WRAP_CONTENT; 538 lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; 539 lp.gravity = Gravity.START | Gravity.BOTTOM; 540 } 541 break; 542 default: 543 throw new IllegalArgumentException("Invalid navigation mode " + mode); 544 } 545 } 546 } 547 548 private void ensureSpinner() { 549 if (mSpinner == null) { 550 mSpinner = new SpinnerCompat(getContext(), null, R.attr.actionDropDownStyle); 551 Toolbar.LayoutParams lp = new Toolbar.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 552 ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL); 553 mSpinner.setLayoutParams(lp); 554 } 555 } 556 557 @Override 558 public void setDropdownParams(SpinnerAdapter adapter, 559 AdapterViewCompat.OnItemSelectedListener listener) { 560 ensureSpinner(); 561 mSpinner.setAdapter(adapter); 562 mSpinner.setOnItemSelectedListener(listener); 563 } 564 565 @Override 566 public void setDropdownSelectedPosition(int position) { 567 if (mSpinner == null) { 568 throw new IllegalStateException( 569 "Can't set dropdown selected position without an adapter"); 570 } 571 mSpinner.setSelection(position); 572 } 573 574 @Override 575 public int getDropdownSelectedPosition() { 576 return mSpinner != null ? mSpinner.getSelectedItemPosition() : 0; 577 } 578 579 @Override 580 public int getDropdownItemCount() { 581 return mSpinner != null ? mSpinner.getCount() : 0; 582 } 583 584 @Override 585 public void setCustomView(View view) { 586 if (mCustomView != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) { 587 mToolbar.removeView(mCustomView); 588 } 589 mCustomView = view; 590 if (view != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) { 591 mToolbar.addView(mCustomView); 592 } 593 } 594 595 @Override 596 public View getCustomView() { 597 return mCustomView; 598 } 599 600 @Override 601 public void animateToVisibility(int visibility) { 602 if (visibility == View.GONE) { 603 ViewCompat.animate(mToolbar).alpha(0) 604 .setListener(new ViewPropertyAnimatorListenerAdapter() { 605 private boolean mCanceled = false; 606 @Override 607 public void onAnimationEnd(View view) { 608 if (!mCanceled) { 609 mToolbar.setVisibility(View.GONE); 610 } 611 } 612 613 @Override 614 public void onAnimationCancel(View view) { 615 mCanceled = true; 616 } 617 }); 618 } else if (visibility == View.VISIBLE) { 619 ViewCompat.animate(mToolbar).alpha(1) 620 .setListener(new ViewPropertyAnimatorListenerAdapter() { 621 @Override 622 public void onAnimationStart(View view) { 623 mToolbar.setVisibility(View.VISIBLE); 624 } 625 }); 626 } 627 } 628 629 @Override 630 public void setNavigationIcon(Drawable icon) { 631 mNavIcon = icon; 632 updateNavigationIcon(); 633 } 634 635 @Override 636 public void setNavigationIcon(int resId) { 637 setNavigationIcon(resId != 0 638 ? mTintManager.getDrawable(resId) 639 : null); 640 } 641 642 @Override 643 public void setNavigationContentDescription(CharSequence description) { 644 mHomeDescription = description; 645 updateHomeAccessibility(); 646 } 647 648 @Override 649 public void setNavigationContentDescription(int resId) { 650 setNavigationContentDescription(resId == 0 ? null : getContext().getString(resId)); 651 } 652 653 private void updateHomeAccessibility() { 654 if ((mDisplayOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) { 655 if (TextUtils.isEmpty(mHomeDescription)) { 656 mToolbar.setNavigationContentDescription(mDefaultNavigationContentDescription); 657 } else { 658 mToolbar.setNavigationContentDescription(mHomeDescription); 659 } 660 } 661 } 662 663 private void updateNavigationIcon() { 664 if ((mDisplayOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) { 665 mToolbar.setNavigationIcon(mNavIcon != null ? mNavIcon : mDefaultNavigationIcon); 666 } 667 } 668 669 @Override 670 public void saveHierarchyState(SparseArray<Parcelable> toolbarStates) { 671 mToolbar.saveHierarchyState(toolbarStates); 672 } 673 674 @Override 675 public void restoreHierarchyState(SparseArray<Parcelable> toolbarStates) { 676 mToolbar.restoreHierarchyState(toolbarStates); 677 } 678 679}