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