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