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