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.widget; 18 19import android.annotation.ColorInt; 20import android.annotation.DrawableRes; 21import android.annotation.MenuRes; 22import android.annotation.NonNull; 23import android.annotation.Nullable; 24import android.annotation.StringRes; 25import android.annotation.StyleRes; 26import android.annotation.TestApi; 27import android.app.ActionBar; 28import android.content.Context; 29import android.content.res.TypedArray; 30import android.graphics.drawable.Drawable; 31import android.os.Parcel; 32import android.os.Parcelable; 33import android.text.Layout; 34import android.text.TextUtils; 35import android.util.AttributeSet; 36import android.view.CollapsibleActionView; 37import android.view.ContextThemeWrapper; 38import android.view.Gravity; 39import android.view.Menu; 40import android.view.MenuInflater; 41import android.view.MenuItem; 42import android.view.MotionEvent; 43import android.view.View; 44import android.view.ViewGroup; 45 46import com.android.internal.R; 47import com.android.internal.view.menu.MenuBuilder; 48import com.android.internal.view.menu.MenuItemImpl; 49import com.android.internal.view.menu.MenuPresenter; 50import com.android.internal.view.menu.MenuView; 51import com.android.internal.view.menu.SubMenuBuilder; 52import com.android.internal.widget.DecorToolbar; 53import com.android.internal.widget.ToolbarWidgetWrapper; 54 55import java.util.ArrayList; 56import java.util.List; 57 58/** 59 * A standard toolbar for use within application content. 60 * 61 * <p>A Toolbar is a generalization of {@link android.app.ActionBar action bars} for use 62 * within application layouts. While an action bar is traditionally part of an 63 * {@link android.app.Activity Activity's} opaque window decor controlled by the framework, 64 * a Toolbar may be placed at any arbitrary level of nesting within a view hierarchy. 65 * An application may choose to designate a Toolbar as the action bar for an Activity 66 * using the {@link android.app.Activity#setActionBar(Toolbar) setActionBar()} method.</p> 67 * 68 * <p>Toolbar supports a more focused feature set than ActionBar. From start to end, a toolbar 69 * may contain a combination of the following optional elements: 70 * 71 * <ul> 72 * <li><em>A navigation button.</em> This may be an Up arrow, navigation menu toggle, close, 73 * collapse, done or another glyph of the app's choosing. This button should always be used 74 * to access other navigational destinations within the container of the Toolbar and 75 * its signified content or otherwise leave the current context signified by the Toolbar. 76 * The navigation button is vertically aligned within the Toolbar's 77 * {@link android.R.styleable#View_minHeight minimum height}, if set.</li> 78 * <li><em>A branded logo image.</em> This may extend to the height of the bar and can be 79 * arbitrarily wide.</li> 80 * <li><em>A title and subtitle.</em> The title should be a signpost for the Toolbar's current 81 * position in the navigation hierarchy and the content contained there. The subtitle, 82 * if present should indicate any extended information about the current content. 83 * If an app uses a logo image it should strongly consider omitting a title and subtitle.</li> 84 * <li><em>One or more custom views.</em> The application may add arbitrary child views 85 * to the Toolbar. They will appear at this position within the layout. If a child view's 86 * {@link LayoutParams} indicates a {@link Gravity} value of 87 * {@link Gravity#CENTER_HORIZONTAL CENTER_HORIZONTAL} the view will attempt to center 88 * within the available space remaining in the Toolbar after all other elements have been 89 * measured.</li> 90 * <li><em>An {@link ActionMenuView action menu}.</em> The menu of actions will pin to the 91 * end of the Toolbar offering a few 92 * <a href="http://developer.android.com/design/patterns/actionbar.html#ActionButtons"> 93 * frequent, important or typical</a> actions along with an optional overflow menu for 94 * additional actions. Action buttons are vertically aligned within the Toolbar's 95 * {@link android.R.styleable#View_minHeight minimum height}, if set.</li> 96 * </ul> 97 * </p> 98 * 99 * <p>In modern Android UIs developers should lean more on a visually distinct color scheme for 100 * toolbars than on their application icon. The use of application icon plus title as a standard 101 * layout is discouraged on API 21 devices and newer.</p> 102 * 103 * @attr ref android.R.styleable#Toolbar_buttonGravity 104 * @attr ref android.R.styleable#Toolbar_collapseContentDescription 105 * @attr ref android.R.styleable#Toolbar_collapseIcon 106 * @attr ref android.R.styleable#Toolbar_contentInsetEnd 107 * @attr ref android.R.styleable#Toolbar_contentInsetLeft 108 * @attr ref android.R.styleable#Toolbar_contentInsetRight 109 * @attr ref android.R.styleable#Toolbar_contentInsetStart 110 * @attr ref android.R.styleable#Toolbar_contentInsetStartWithNavigation 111 * @attr ref android.R.styleable#Toolbar_contentInsetEndWithActions 112 * @attr ref android.R.styleable#Toolbar_gravity 113 * @attr ref android.R.styleable#Toolbar_logo 114 * @attr ref android.R.styleable#Toolbar_logoDescription 115 * @attr ref android.R.styleable#Toolbar_maxButtonHeight 116 * @attr ref android.R.styleable#Toolbar_navigationContentDescription 117 * @attr ref android.R.styleable#Toolbar_navigationIcon 118 * @attr ref android.R.styleable#Toolbar_popupTheme 119 * @attr ref android.R.styleable#Toolbar_subtitle 120 * @attr ref android.R.styleable#Toolbar_subtitleTextAppearance 121 * @attr ref android.R.styleable#Toolbar_subtitleTextColor 122 * @attr ref android.R.styleable#Toolbar_title 123 * @attr ref android.R.styleable#Toolbar_titleMargin 124 * @attr ref android.R.styleable#Toolbar_titleMarginBottom 125 * @attr ref android.R.styleable#Toolbar_titleMarginEnd 126 * @attr ref android.R.styleable#Toolbar_titleMarginStart 127 * @attr ref android.R.styleable#Toolbar_titleMarginTop 128 * @attr ref android.R.styleable#Toolbar_titleTextAppearance 129 * @attr ref android.R.styleable#Toolbar_titleTextColor 130 */ 131public class Toolbar extends ViewGroup { 132 private static final String TAG = "Toolbar"; 133 134 private ActionMenuView mMenuView; 135 private TextView mTitleTextView; 136 private TextView mSubtitleTextView; 137 private ImageButton mNavButtonView; 138 private ImageView mLogoView; 139 140 private Drawable mCollapseIcon; 141 private CharSequence mCollapseDescription; 142 private ImageButton mCollapseButtonView; 143 View mExpandedActionView; 144 145 /** Context against which to inflate popup menus. */ 146 private Context mPopupContext; 147 148 /** Theme resource against which to inflate popup menus. */ 149 private int mPopupTheme; 150 151 private int mTitleTextAppearance; 152 private int mSubtitleTextAppearance; 153 private int mNavButtonStyle; 154 155 private int mButtonGravity; 156 157 private int mMaxButtonHeight; 158 159 private int mTitleMarginStart; 160 private int mTitleMarginEnd; 161 private int mTitleMarginTop; 162 private int mTitleMarginBottom; 163 164 private final RtlSpacingHelper mContentInsets = new RtlSpacingHelper(); 165 private int mContentInsetStartWithNavigation; 166 private int mContentInsetEndWithActions; 167 168 private int mGravity = Gravity.START | Gravity.CENTER_VERTICAL; 169 170 private CharSequence mTitleText; 171 private CharSequence mSubtitleText; 172 173 private int mTitleTextColor; 174 private int mSubtitleTextColor; 175 176 private boolean mEatingTouch; 177 178 // Clear me after use. 179 private final ArrayList<View> mTempViews = new ArrayList<View>(); 180 181 // Used to hold views that will be removed while we have an expanded action view. 182 private final ArrayList<View> mHiddenViews = new ArrayList<>(); 183 184 private final int[] mTempMargins = new int[2]; 185 186 private OnMenuItemClickListener mOnMenuItemClickListener; 187 188 private final ActionMenuView.OnMenuItemClickListener mMenuViewItemClickListener = 189 new ActionMenuView.OnMenuItemClickListener() { 190 @Override 191 public boolean onMenuItemClick(MenuItem item) { 192 if (mOnMenuItemClickListener != null) { 193 return mOnMenuItemClickListener.onMenuItemClick(item); 194 } 195 return false; 196 } 197 }; 198 199 private ToolbarWidgetWrapper mWrapper; 200 private ActionMenuPresenter mOuterActionMenuPresenter; 201 private ExpandedActionViewMenuPresenter mExpandedMenuPresenter; 202 private MenuPresenter.Callback mActionMenuPresenterCallback; 203 private MenuBuilder.Callback mMenuBuilderCallback; 204 205 private boolean mCollapsible; 206 207 private final Runnable mShowOverflowMenuRunnable = new Runnable() { 208 @Override public void run() { 209 showOverflowMenu(); 210 } 211 }; 212 213 public Toolbar(Context context) { 214 this(context, null); 215 } 216 217 public Toolbar(Context context, AttributeSet attrs) { 218 this(context, attrs, com.android.internal.R.attr.toolbarStyle); 219 } 220 221 public Toolbar(Context context, AttributeSet attrs, int defStyleAttr) { 222 this(context, attrs, defStyleAttr, 0); 223 } 224 225 public Toolbar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 226 super(context, attrs, defStyleAttr, defStyleRes); 227 228 final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Toolbar, 229 defStyleAttr, defStyleRes); 230 231 mTitleTextAppearance = a.getResourceId(R.styleable.Toolbar_titleTextAppearance, 0); 232 mSubtitleTextAppearance = a.getResourceId(R.styleable.Toolbar_subtitleTextAppearance, 0); 233 mNavButtonStyle = a.getResourceId(R.styleable.Toolbar_navigationButtonStyle, 0); 234 mGravity = a.getInteger(R.styleable.Toolbar_gravity, mGravity); 235 mButtonGravity = a.getInteger(R.styleable.Toolbar_buttonGravity, Gravity.TOP); 236 mTitleMarginStart = mTitleMarginEnd = mTitleMarginTop = mTitleMarginBottom = 237 a.getDimensionPixelOffset(R.styleable.Toolbar_titleMargin, 0); 238 239 final int marginStart = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginStart, -1); 240 if (marginStart >= 0) { 241 mTitleMarginStart = marginStart; 242 } 243 244 final int marginEnd = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginEnd, -1); 245 if (marginEnd >= 0) { 246 mTitleMarginEnd = marginEnd; 247 } 248 249 final int marginTop = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginTop, -1); 250 if (marginTop >= 0) { 251 mTitleMarginTop = marginTop; 252 } 253 254 final int marginBottom = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginBottom, 255 -1); 256 if (marginBottom >= 0) { 257 mTitleMarginBottom = marginBottom; 258 } 259 260 mMaxButtonHeight = a.getDimensionPixelSize(R.styleable.Toolbar_maxButtonHeight, -1); 261 262 final int contentInsetStart = 263 a.getDimensionPixelOffset(R.styleable.Toolbar_contentInsetStart, 264 RtlSpacingHelper.UNDEFINED); 265 final int contentInsetEnd = 266 a.getDimensionPixelOffset(R.styleable.Toolbar_contentInsetEnd, 267 RtlSpacingHelper.UNDEFINED); 268 final int contentInsetLeft = 269 a.getDimensionPixelSize(R.styleable.Toolbar_contentInsetLeft, 0); 270 final int contentInsetRight = 271 a.getDimensionPixelSize(R.styleable.Toolbar_contentInsetRight, 0); 272 273 mContentInsets.setAbsolute(contentInsetLeft, contentInsetRight); 274 275 if (contentInsetStart != RtlSpacingHelper.UNDEFINED || 276 contentInsetEnd != RtlSpacingHelper.UNDEFINED) { 277 mContentInsets.setRelative(contentInsetStart, contentInsetEnd); 278 } 279 280 mContentInsetStartWithNavigation = a.getDimensionPixelOffset( 281 R.styleable.Toolbar_contentInsetStartWithNavigation, RtlSpacingHelper.UNDEFINED); 282 mContentInsetEndWithActions = a.getDimensionPixelOffset( 283 R.styleable.Toolbar_contentInsetEndWithActions, RtlSpacingHelper.UNDEFINED); 284 285 mCollapseIcon = a.getDrawable(R.styleable.Toolbar_collapseIcon); 286 mCollapseDescription = a.getText(R.styleable.Toolbar_collapseContentDescription); 287 288 final CharSequence title = a.getText(R.styleable.Toolbar_title); 289 if (!TextUtils.isEmpty(title)) { 290 setTitle(title); 291 } 292 293 final CharSequence subtitle = a.getText(R.styleable.Toolbar_subtitle); 294 if (!TextUtils.isEmpty(subtitle)) { 295 setSubtitle(subtitle); 296 } 297 298 // Set the default context, since setPopupTheme() may be a no-op. 299 mPopupContext = mContext; 300 setPopupTheme(a.getResourceId(R.styleable.Toolbar_popupTheme, 0)); 301 302 final Drawable navIcon = a.getDrawable(R.styleable.Toolbar_navigationIcon); 303 if (navIcon != null) { 304 setNavigationIcon(navIcon); 305 } 306 307 final CharSequence navDesc = a.getText( 308 R.styleable.Toolbar_navigationContentDescription); 309 if (!TextUtils.isEmpty(navDesc)) { 310 setNavigationContentDescription(navDesc); 311 } 312 313 final Drawable logo = a.getDrawable(R.styleable.Toolbar_logo); 314 if (logo != null) { 315 setLogo(logo); 316 } 317 318 final CharSequence logoDesc = a.getText(R.styleable.Toolbar_logoDescription); 319 if (!TextUtils.isEmpty(logoDesc)) { 320 setLogoDescription(logoDesc); 321 } 322 323 if (a.hasValue(R.styleable.Toolbar_titleTextColor)) { 324 setTitleTextColor(a.getColor(R.styleable.Toolbar_titleTextColor, 0xffffffff)); 325 } 326 327 if (a.hasValue(R.styleable.Toolbar_subtitleTextColor)) { 328 setSubtitleTextColor(a.getColor(R.styleable.Toolbar_subtitleTextColor, 0xffffffff)); 329 } 330 a.recycle(); 331 } 332 333 /** 334 * Specifies the theme to use when inflating popup menus. By default, uses 335 * the same theme as the toolbar itself. 336 * 337 * @param resId theme used to inflate popup menus 338 * @see #getPopupTheme() 339 */ 340 public void setPopupTheme(@StyleRes int resId) { 341 if (mPopupTheme != resId) { 342 mPopupTheme = resId; 343 if (resId == 0) { 344 mPopupContext = mContext; 345 } else { 346 mPopupContext = new ContextThemeWrapper(mContext, resId); 347 } 348 } 349 } 350 351 /** 352 * @return resource identifier of the theme used to inflate popup menus, or 353 * 0 if menus are inflated against the toolbar theme 354 * @see #setPopupTheme(int) 355 */ 356 public int getPopupTheme() { 357 return mPopupTheme; 358 } 359 360 /** 361 * Sets the title margin. 362 * 363 * @param start the starting title margin in pixels 364 * @param top the top title margin in pixels 365 * @param end the ending title margin in pixels 366 * @param bottom the bottom title margin in pixels 367 * @see #getTitleMarginStart() 368 * @see #getTitleMarginTop() 369 * @see #getTitleMarginEnd() 370 * @see #getTitleMarginBottom() 371 * @attr ref android.R.styleable#Toolbar_titleMargin 372 */ 373 public void setTitleMargin(int start, int top, int end, int bottom) { 374 mTitleMarginStart = start; 375 mTitleMarginTop = top; 376 mTitleMarginEnd = end; 377 mTitleMarginBottom = bottom; 378 379 requestLayout(); 380 } 381 382 /** 383 * @return the starting title margin in pixels 384 * @see #setTitleMarginStart(int) 385 * @attr ref android.R.styleable#Toolbar_titleMarginStart 386 */ 387 public int getTitleMarginStart() { 388 return mTitleMarginStart; 389 } 390 391 /** 392 * Sets the starting title margin in pixels. 393 * 394 * @param margin the starting title margin in pixels 395 * @see #getTitleMarginStart() 396 * @attr ref android.R.styleable#Toolbar_titleMarginStart 397 */ 398 public void setTitleMarginStart(int margin) { 399 mTitleMarginStart = margin; 400 401 requestLayout(); 402 } 403 404 /** 405 * @return the top title margin in pixels 406 * @see #setTitleMarginTop(int) 407 * @attr ref android.R.styleable#Toolbar_titleMarginTop 408 */ 409 public int getTitleMarginTop() { 410 return mTitleMarginTop; 411 } 412 413 /** 414 * Sets the top title margin in pixels. 415 * 416 * @param margin the top title margin in pixels 417 * @see #getTitleMarginTop() 418 * @attr ref android.R.styleable#Toolbar_titleMarginTop 419 */ 420 public void setTitleMarginTop(int margin) { 421 mTitleMarginTop = margin; 422 423 requestLayout(); 424 } 425 426 /** 427 * @return the ending title margin in pixels 428 * @see #setTitleMarginEnd(int) 429 * @attr ref android.R.styleable#Toolbar_titleMarginEnd 430 */ 431 public int getTitleMarginEnd() { 432 return mTitleMarginEnd; 433 } 434 435 /** 436 * Sets the ending title margin in pixels. 437 * 438 * @param margin the ending title margin in pixels 439 * @see #getTitleMarginEnd() 440 * @attr ref android.R.styleable#Toolbar_titleMarginEnd 441 */ 442 public void setTitleMarginEnd(int margin) { 443 mTitleMarginEnd = margin; 444 445 requestLayout(); 446 } 447 448 /** 449 * @return the bottom title margin in pixels 450 * @see #setTitleMarginBottom(int) 451 * @attr ref android.R.styleable#Toolbar_titleMarginBottom 452 */ 453 public int getTitleMarginBottom() { 454 return mTitleMarginBottom; 455 } 456 457 /** 458 * Sets the bottom title margin in pixels. 459 * 460 * @param margin the bottom title margin in pixels 461 * @see #getTitleMarginBottom() 462 * @attr ref android.R.styleable#Toolbar_titleMarginBottom 463 */ 464 public void setTitleMarginBottom(int margin) { 465 mTitleMarginBottom = margin; 466 467 requestLayout(); 468 } 469 470 @Override 471 public void onRtlPropertiesChanged(@ResolvedLayoutDir int layoutDirection) { 472 super.onRtlPropertiesChanged(layoutDirection); 473 mContentInsets.setDirection(layoutDirection == LAYOUT_DIRECTION_RTL); 474 } 475 476 /** 477 * Set a logo drawable from a resource id. 478 * 479 * <p>This drawable should generally take the place of title text. The logo cannot be 480 * clicked. Apps using a logo should also supply a description using 481 * {@link #setLogoDescription(int)}.</p> 482 * 483 * @param resId ID of a drawable resource 484 */ 485 public void setLogo(@DrawableRes int resId) { 486 setLogo(getContext().getDrawable(resId)); 487 } 488 489 /** @hide */ 490 public boolean canShowOverflowMenu() { 491 return getVisibility() == VISIBLE && mMenuView != null && mMenuView.isOverflowReserved(); 492 } 493 494 /** 495 * Check whether the overflow menu is currently showing. This may not reflect 496 * a pending show operation in progress. 497 * 498 * @return true if the overflow menu is currently showing 499 */ 500 public boolean isOverflowMenuShowing() { 501 return mMenuView != null && mMenuView.isOverflowMenuShowing(); 502 } 503 504 /** @hide */ 505 public boolean isOverflowMenuShowPending() { 506 return mMenuView != null && mMenuView.isOverflowMenuShowPending(); 507 } 508 509 /** 510 * Show the overflow items from the associated menu. 511 * 512 * @return true if the menu was able to be shown, false otherwise 513 */ 514 public boolean showOverflowMenu() { 515 return mMenuView != null && mMenuView.showOverflowMenu(); 516 } 517 518 /** 519 * Hide the overflow items from the associated menu. 520 * 521 * @return true if the menu was able to be hidden, false otherwise 522 */ 523 public boolean hideOverflowMenu() { 524 return mMenuView != null && mMenuView.hideOverflowMenu(); 525 } 526 527 /** @hide */ 528 public void setMenu(MenuBuilder menu, ActionMenuPresenter outerPresenter) { 529 if (menu == null && mMenuView == null) { 530 return; 531 } 532 533 ensureMenuView(); 534 final MenuBuilder oldMenu = mMenuView.peekMenu(); 535 if (oldMenu == menu) { 536 return; 537 } 538 539 if (oldMenu != null) { 540 oldMenu.removeMenuPresenter(mOuterActionMenuPresenter); 541 oldMenu.removeMenuPresenter(mExpandedMenuPresenter); 542 } 543 544 if (mExpandedMenuPresenter == null) { 545 mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter(); 546 } 547 548 outerPresenter.setExpandedActionViewsExclusive(true); 549 if (menu != null) { 550 menu.addMenuPresenter(outerPresenter, mPopupContext); 551 menu.addMenuPresenter(mExpandedMenuPresenter, mPopupContext); 552 } else { 553 outerPresenter.initForMenu(mPopupContext, null); 554 mExpandedMenuPresenter.initForMenu(mPopupContext, null); 555 outerPresenter.updateMenuView(true); 556 mExpandedMenuPresenter.updateMenuView(true); 557 } 558 mMenuView.setPopupTheme(mPopupTheme); 559 mMenuView.setPresenter(outerPresenter); 560 mOuterActionMenuPresenter = outerPresenter; 561 } 562 563 /** 564 * Dismiss all currently showing popup menus, including overflow or submenus. 565 */ 566 public void dismissPopupMenus() { 567 if (mMenuView != null) { 568 mMenuView.dismissPopupMenus(); 569 } 570 } 571 572 /** @hide */ 573 public boolean isTitleTruncated() { 574 if (mTitleTextView == null) { 575 return false; 576 } 577 578 final Layout titleLayout = mTitleTextView.getLayout(); 579 if (titleLayout == null) { 580 return false; 581 } 582 583 final int lineCount = titleLayout.getLineCount(); 584 for (int i = 0; i < lineCount; i++) { 585 if (titleLayout.getEllipsisCount(i) > 0) { 586 return true; 587 } 588 } 589 return false; 590 } 591 592 /** 593 * Set a logo drawable. 594 * 595 * <p>This drawable should generally take the place of title text. The logo cannot be 596 * clicked. Apps using a logo should also supply a description using 597 * {@link #setLogoDescription(int)}.</p> 598 * 599 * @param drawable Drawable to use as a logo 600 */ 601 public void setLogo(Drawable drawable) { 602 if (drawable != null) { 603 ensureLogoView(); 604 if (!isChildOrHidden(mLogoView)) { 605 addSystemView(mLogoView, true); 606 } 607 } else if (mLogoView != null && isChildOrHidden(mLogoView)) { 608 removeView(mLogoView); 609 mHiddenViews.remove(mLogoView); 610 } 611 if (mLogoView != null) { 612 mLogoView.setImageDrawable(drawable); 613 } 614 } 615 616 /** 617 * Return the current logo drawable. 618 * 619 * @return The current logo drawable 620 * @see #setLogo(int) 621 * @see #setLogo(android.graphics.drawable.Drawable) 622 */ 623 public Drawable getLogo() { 624 return mLogoView != null ? mLogoView.getDrawable() : null; 625 } 626 627 /** 628 * Set a description of the toolbar's logo. 629 * 630 * <p>This description will be used for accessibility or other similar descriptions 631 * of the UI.</p> 632 * 633 * @param resId String resource id 634 */ 635 public void setLogoDescription(@StringRes int resId) { 636 setLogoDescription(getContext().getText(resId)); 637 } 638 639 /** 640 * Set a description of the toolbar's logo. 641 * 642 * <p>This description will be used for accessibility or other similar descriptions 643 * of the UI.</p> 644 * 645 * @param description Description to set 646 */ 647 public void setLogoDescription(CharSequence description) { 648 if (!TextUtils.isEmpty(description)) { 649 ensureLogoView(); 650 } 651 if (mLogoView != null) { 652 mLogoView.setContentDescription(description); 653 } 654 } 655 656 /** 657 * Return the description of the toolbar's logo. 658 * 659 * @return A description of the logo 660 */ 661 public CharSequence getLogoDescription() { 662 return mLogoView != null ? mLogoView.getContentDescription() : null; 663 } 664 665 private void ensureLogoView() { 666 if (mLogoView == null) { 667 mLogoView = new ImageView(getContext()); 668 } 669 } 670 671 /** 672 * Check whether this Toolbar is currently hosting an expanded action view. 673 * 674 * <p>An action view may be expanded either directly from the 675 * {@link android.view.MenuItem MenuItem} it belongs to or by user action. If the Toolbar 676 * has an expanded action view it can be collapsed using the {@link #collapseActionView()} 677 * method.</p> 678 * 679 * @return true if the Toolbar has an expanded action view 680 */ 681 public boolean hasExpandedActionView() { 682 return mExpandedMenuPresenter != null && 683 mExpandedMenuPresenter.mCurrentExpandedItem != null; 684 } 685 686 /** 687 * Collapse a currently expanded action view. If this Toolbar does not have an 688 * expanded action view this method has no effect. 689 * 690 * <p>An action view may be expanded either directly from the 691 * {@link android.view.MenuItem MenuItem} it belongs to or by user action.</p> 692 * 693 * @see #hasExpandedActionView() 694 */ 695 public void collapseActionView() { 696 final MenuItemImpl item = mExpandedMenuPresenter == null ? null : 697 mExpandedMenuPresenter.mCurrentExpandedItem; 698 if (item != null) { 699 item.collapseActionView(); 700 } 701 } 702 703 /** 704 * Returns the title of this toolbar. 705 * 706 * @return The current title. 707 */ 708 public CharSequence getTitle() { 709 return mTitleText; 710 } 711 712 /** 713 * Set the title of this toolbar. 714 * 715 * <p>A title should be used as the anchor for a section of content. It should 716 * describe or name the content being viewed.</p> 717 * 718 * @param resId Resource ID of a string to set as the title 719 */ 720 public void setTitle(@StringRes int resId) { 721 setTitle(getContext().getText(resId)); 722 } 723 724 /** 725 * Set the title of this toolbar. 726 * 727 * <p>A title should be used as the anchor for a section of content. It should 728 * describe or name the content being viewed.</p> 729 * 730 * @param title Title to set 731 */ 732 public void setTitle(CharSequence title) { 733 if (!TextUtils.isEmpty(title)) { 734 if (mTitleTextView == null) { 735 final Context context = getContext(); 736 mTitleTextView = new TextView(context); 737 mTitleTextView.setSingleLine(); 738 mTitleTextView.setEllipsize(TextUtils.TruncateAt.END); 739 if (mTitleTextAppearance != 0) { 740 mTitleTextView.setTextAppearance(mTitleTextAppearance); 741 } 742 if (mTitleTextColor != 0) { 743 mTitleTextView.setTextColor(mTitleTextColor); 744 } 745 } 746 if (!isChildOrHidden(mTitleTextView)) { 747 addSystemView(mTitleTextView, true); 748 } 749 } else if (mTitleTextView != null && isChildOrHidden(mTitleTextView)) { 750 removeView(mTitleTextView); 751 mHiddenViews.remove(mTitleTextView); 752 } 753 if (mTitleTextView != null) { 754 mTitleTextView.setText(title); 755 } 756 mTitleText = title; 757 } 758 759 /** 760 * Return the subtitle of this toolbar. 761 * 762 * @return The current subtitle 763 */ 764 public CharSequence getSubtitle() { 765 return mSubtitleText; 766 } 767 768 /** 769 * Set the subtitle of this toolbar. 770 * 771 * <p>Subtitles should express extended information about the current content.</p> 772 * 773 * @param resId String resource ID 774 */ 775 public void setSubtitle(@StringRes int resId) { 776 setSubtitle(getContext().getText(resId)); 777 } 778 779 /** 780 * Set the subtitle of this toolbar. 781 * 782 * <p>Subtitles should express extended information about the current content.</p> 783 * 784 * @param subtitle Subtitle to set 785 */ 786 public void setSubtitle(CharSequence subtitle) { 787 if (!TextUtils.isEmpty(subtitle)) { 788 if (mSubtitleTextView == null) { 789 final Context context = getContext(); 790 mSubtitleTextView = new TextView(context); 791 mSubtitleTextView.setSingleLine(); 792 mSubtitleTextView.setEllipsize(TextUtils.TruncateAt.END); 793 if (mSubtitleTextAppearance != 0) { 794 mSubtitleTextView.setTextAppearance(mSubtitleTextAppearance); 795 } 796 if (mSubtitleTextColor != 0) { 797 mSubtitleTextView.setTextColor(mSubtitleTextColor); 798 } 799 } 800 if (!isChildOrHidden(mSubtitleTextView)) { 801 addSystemView(mSubtitleTextView, true); 802 } 803 } else if (mSubtitleTextView != null && isChildOrHidden(mSubtitleTextView)) { 804 removeView(mSubtitleTextView); 805 mHiddenViews.remove(mSubtitleTextView); 806 } 807 if (mSubtitleTextView != null) { 808 mSubtitleTextView.setText(subtitle); 809 } 810 mSubtitleText = subtitle; 811 } 812 813 /** 814 * Sets the text color, size, style, hint color, and highlight color 815 * from the specified TextAppearance resource. 816 */ 817 public void setTitleTextAppearance(Context context, @StyleRes int resId) { 818 mTitleTextAppearance = resId; 819 if (mTitleTextView != null) { 820 mTitleTextView.setTextAppearance(resId); 821 } 822 } 823 824 /** 825 * Sets the text color, size, style, hint color, and highlight color 826 * from the specified TextAppearance resource. 827 */ 828 public void setSubtitleTextAppearance(Context context, @StyleRes int resId) { 829 mSubtitleTextAppearance = resId; 830 if (mSubtitleTextView != null) { 831 mSubtitleTextView.setTextAppearance(resId); 832 } 833 } 834 835 /** 836 * Sets the text color of the title, if present. 837 * 838 * @param color The new text color in 0xAARRGGBB format 839 */ 840 public void setTitleTextColor(@ColorInt int color) { 841 mTitleTextColor = color; 842 if (mTitleTextView != null) { 843 mTitleTextView.setTextColor(color); 844 } 845 } 846 847 /** 848 * Sets the text color of the subtitle, if present. 849 * 850 * @param color The new text color in 0xAARRGGBB format 851 */ 852 public void setSubtitleTextColor(@ColorInt int color) { 853 mSubtitleTextColor = color; 854 if (mSubtitleTextView != null) { 855 mSubtitleTextView.setTextColor(color); 856 } 857 } 858 859 /** 860 * Retrieve the currently configured content description for the navigation button view. 861 * This will be used to describe the navigation action to users through mechanisms such 862 * as screen readers or tooltips. 863 * 864 * @return The navigation button's content description 865 * 866 * @attr ref android.R.styleable#Toolbar_navigationContentDescription 867 */ 868 @Nullable 869 public CharSequence getNavigationContentDescription() { 870 return mNavButtonView != null ? mNavButtonView.getContentDescription() : null; 871 } 872 873 /** 874 * Set a content description for the navigation button if one is present. The content 875 * description will be read via screen readers or other accessibility systems to explain 876 * the action of the navigation button. 877 * 878 * @param resId Resource ID of a content description string to set, or 0 to 879 * clear the description 880 * 881 * @attr ref android.R.styleable#Toolbar_navigationContentDescription 882 */ 883 public void setNavigationContentDescription(@StringRes int resId) { 884 setNavigationContentDescription(resId != 0 ? getContext().getText(resId) : null); 885 } 886 887 /** 888 * Set a content description for the navigation button if one is present. The content 889 * description will be read via screen readers or other accessibility systems to explain 890 * the action of the navigation button. 891 * 892 * @param description Content description to set, or <code>null</code> to 893 * clear the content description 894 * 895 * @attr ref android.R.styleable#Toolbar_navigationContentDescription 896 */ 897 public void setNavigationContentDescription(@Nullable CharSequence description) { 898 if (!TextUtils.isEmpty(description)) { 899 ensureNavButtonView(); 900 } 901 if (mNavButtonView != null) { 902 mNavButtonView.setContentDescription(description); 903 } 904 } 905 906 /** 907 * Set the icon to use for the toolbar's navigation button. 908 * 909 * <p>The navigation button appears at the start of the toolbar if present. Setting an icon 910 * will make the navigation button visible.</p> 911 * 912 * <p>If you use a navigation icon you should also set a description for its action using 913 * {@link #setNavigationContentDescription(int)}. This is used for accessibility and 914 * tooltips.</p> 915 * 916 * @param resId Resource ID of a drawable to set 917 * 918 * @attr ref android.R.styleable#Toolbar_navigationIcon 919 */ 920 public void setNavigationIcon(@DrawableRes int resId) { 921 setNavigationIcon(getContext().getDrawable(resId)); 922 } 923 924 /** 925 * Set the icon to use for the toolbar's navigation button. 926 * 927 * <p>The navigation button appears at the start of the toolbar if present. Setting an icon 928 * will make the navigation button visible.</p> 929 * 930 * <p>If you use a navigation icon you should also set a description for its action using 931 * {@link #setNavigationContentDescription(int)}. This is used for accessibility and 932 * tooltips.</p> 933 * 934 * @param icon Drawable to set, may be null to clear the icon 935 * 936 * @attr ref android.R.styleable#Toolbar_navigationIcon 937 */ 938 public void setNavigationIcon(@Nullable Drawable icon) { 939 if (icon != null) { 940 ensureNavButtonView(); 941 if (!isChildOrHidden(mNavButtonView)) { 942 addSystemView(mNavButtonView, true); 943 } 944 } else if (mNavButtonView != null && isChildOrHidden(mNavButtonView)) { 945 removeView(mNavButtonView); 946 mHiddenViews.remove(mNavButtonView); 947 } 948 if (mNavButtonView != null) { 949 mNavButtonView.setImageDrawable(icon); 950 } 951 } 952 953 /** 954 * Return the current drawable used as the navigation icon. 955 * 956 * @return The navigation icon drawable 957 * 958 * @attr ref android.R.styleable#Toolbar_navigationIcon 959 */ 960 @Nullable 961 public Drawable getNavigationIcon() { 962 return mNavButtonView != null ? mNavButtonView.getDrawable() : null; 963 } 964 965 /** 966 * Set a listener to respond to navigation events. 967 * 968 * <p>This listener will be called whenever the user clicks the navigation button 969 * at the start of the toolbar. An icon must be set for the navigation button to appear.</p> 970 * 971 * @param listener Listener to set 972 * @see #setNavigationIcon(android.graphics.drawable.Drawable) 973 */ 974 public void setNavigationOnClickListener(OnClickListener listener) { 975 ensureNavButtonView(); 976 mNavButtonView.setOnClickListener(listener); 977 } 978 979 /** 980 * @hide 981 */ 982 @Nullable 983 @TestApi 984 public View getNavigationView() { 985 return mNavButtonView; 986 } 987 988 /** 989 * Return the Menu shown in the toolbar. 990 * 991 * <p>Applications that wish to populate the toolbar's menu can do so from here. To use 992 * an XML menu resource, use {@link #inflateMenu(int)}.</p> 993 * 994 * @return The toolbar's Menu 995 */ 996 public Menu getMenu() { 997 ensureMenu(); 998 return mMenuView.getMenu(); 999 } 1000 1001 /** 1002 * Set the icon to use for the overflow button. 1003 * 1004 * @param icon Drawable to set, may be null to clear the icon 1005 */ 1006 public void setOverflowIcon(@Nullable Drawable icon) { 1007 ensureMenu(); 1008 mMenuView.setOverflowIcon(icon); 1009 } 1010 1011 /** 1012 * Return the current drawable used as the overflow icon. 1013 * 1014 * @return The overflow icon drawable 1015 */ 1016 @Nullable 1017 public Drawable getOverflowIcon() { 1018 ensureMenu(); 1019 return mMenuView.getOverflowIcon(); 1020 } 1021 1022 private void ensureMenu() { 1023 ensureMenuView(); 1024 if (mMenuView.peekMenu() == null) { 1025 // Initialize a new menu for the first time. 1026 final MenuBuilder menu = (MenuBuilder) mMenuView.getMenu(); 1027 if (mExpandedMenuPresenter == null) { 1028 mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter(); 1029 } 1030 mMenuView.setExpandedActionViewsExclusive(true); 1031 menu.addMenuPresenter(mExpandedMenuPresenter, mPopupContext); 1032 } 1033 } 1034 1035 private void ensureMenuView() { 1036 if (mMenuView == null) { 1037 mMenuView = new ActionMenuView(getContext()); 1038 mMenuView.setPopupTheme(mPopupTheme); 1039 mMenuView.setOnMenuItemClickListener(mMenuViewItemClickListener); 1040 mMenuView.setMenuCallbacks(mActionMenuPresenterCallback, mMenuBuilderCallback); 1041 final LayoutParams lp = generateDefaultLayoutParams(); 1042 lp.gravity = Gravity.END | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK); 1043 mMenuView.setLayoutParams(lp); 1044 addSystemView(mMenuView, false); 1045 } 1046 } 1047 1048 private MenuInflater getMenuInflater() { 1049 return new MenuInflater(getContext()); 1050 } 1051 1052 /** 1053 * Inflate a menu resource into this toolbar. 1054 * 1055 * <p>Inflate an XML menu resource into this toolbar. Existing items in the menu will not 1056 * be modified or removed.</p> 1057 * 1058 * @param resId ID of a menu resource to inflate 1059 */ 1060 public void inflateMenu(@MenuRes int resId) { 1061 getMenuInflater().inflate(resId, getMenu()); 1062 } 1063 1064 /** 1065 * Set a listener to respond to menu item click events. 1066 * 1067 * <p>This listener will be invoked whenever a user selects a menu item from 1068 * the action buttons presented at the end of the toolbar or the associated overflow.</p> 1069 * 1070 * @param listener Listener to set 1071 */ 1072 public void setOnMenuItemClickListener(OnMenuItemClickListener listener) { 1073 mOnMenuItemClickListener = listener; 1074 } 1075 1076 /** 1077 * Sets the content insets for this toolbar relative to layout direction. 1078 * 1079 * <p>The content inset affects the valid area for Toolbar content other than 1080 * the navigation button and menu. Insets define the minimum margin for these components 1081 * and can be used to effectively align Toolbar content along well-known gridlines.</p> 1082 * 1083 * @param contentInsetStart Content inset for the toolbar starting edge 1084 * @param contentInsetEnd Content inset for the toolbar ending edge 1085 * 1086 * @see #setContentInsetsAbsolute(int, int) 1087 * @see #getContentInsetStart() 1088 * @see #getContentInsetEnd() 1089 * @see #getContentInsetLeft() 1090 * @see #getContentInsetRight() 1091 * @attr ref android.R.styleable#Toolbar_contentInsetEnd 1092 * @attr ref android.R.styleable#Toolbar_contentInsetStart 1093 */ 1094 public void setContentInsetsRelative(int contentInsetStart, int contentInsetEnd) { 1095 mContentInsets.setRelative(contentInsetStart, contentInsetEnd); 1096 } 1097 1098 /** 1099 * Gets the starting content inset for this toolbar. 1100 * 1101 * <p>The content inset affects the valid area for Toolbar content other than 1102 * the navigation button and menu. Insets define the minimum margin for these components 1103 * and can be used to effectively align Toolbar content along well-known gridlines.</p> 1104 * 1105 * @return The starting content inset for this toolbar 1106 * 1107 * @see #setContentInsetsRelative(int, int) 1108 * @see #setContentInsetsAbsolute(int, int) 1109 * @see #getContentInsetEnd() 1110 * @see #getContentInsetLeft() 1111 * @see #getContentInsetRight() 1112 * @attr ref android.R.styleable#Toolbar_contentInsetStart 1113 */ 1114 public int getContentInsetStart() { 1115 return mContentInsets.getStart(); 1116 } 1117 1118 /** 1119 * Gets the ending content inset for this toolbar. 1120 * 1121 * <p>The content inset affects the valid area for Toolbar content other than 1122 * the navigation button and menu. Insets define the minimum margin for these components 1123 * and can be used to effectively align Toolbar content along well-known gridlines.</p> 1124 * 1125 * @return The ending content inset for this toolbar 1126 * 1127 * @see #setContentInsetsRelative(int, int) 1128 * @see #setContentInsetsAbsolute(int, int) 1129 * @see #getContentInsetStart() 1130 * @see #getContentInsetLeft() 1131 * @see #getContentInsetRight() 1132 * @attr ref android.R.styleable#Toolbar_contentInsetEnd 1133 */ 1134 public int getContentInsetEnd() { 1135 return mContentInsets.getEnd(); 1136 } 1137 1138 /** 1139 * Sets the content insets for this toolbar. 1140 * 1141 * <p>The content inset affects the valid area for Toolbar content other than 1142 * the navigation button and menu. Insets define the minimum margin for these components 1143 * and can be used to effectively align Toolbar content along well-known gridlines.</p> 1144 * 1145 * @param contentInsetLeft Content inset for the toolbar's left edge 1146 * @param contentInsetRight Content inset for the toolbar's right edge 1147 * 1148 * @see #setContentInsetsAbsolute(int, int) 1149 * @see #getContentInsetStart() 1150 * @see #getContentInsetEnd() 1151 * @see #getContentInsetLeft() 1152 * @see #getContentInsetRight() 1153 * @attr ref android.R.styleable#Toolbar_contentInsetLeft 1154 * @attr ref android.R.styleable#Toolbar_contentInsetRight 1155 */ 1156 public void setContentInsetsAbsolute(int contentInsetLeft, int contentInsetRight) { 1157 mContentInsets.setAbsolute(contentInsetLeft, contentInsetRight); 1158 } 1159 1160 /** 1161 * Gets the left content inset for this toolbar. 1162 * 1163 * <p>The content inset affects the valid area for Toolbar content other than 1164 * the navigation button and menu. Insets define the minimum margin for these components 1165 * and can be used to effectively align Toolbar content along well-known gridlines.</p> 1166 * 1167 * @return The left content inset for this toolbar 1168 * 1169 * @see #setContentInsetsRelative(int, int) 1170 * @see #setContentInsetsAbsolute(int, int) 1171 * @see #getContentInsetStart() 1172 * @see #getContentInsetEnd() 1173 * @see #getContentInsetRight() 1174 * @attr ref android.R.styleable#Toolbar_contentInsetLeft 1175 */ 1176 public int getContentInsetLeft() { 1177 return mContentInsets.getLeft(); 1178 } 1179 1180 /** 1181 * Gets the right content inset for this toolbar. 1182 * 1183 * <p>The content inset affects the valid area for Toolbar content other than 1184 * the navigation button and menu. Insets define the minimum margin for these components 1185 * and can be used to effectively align Toolbar content along well-known gridlines.</p> 1186 * 1187 * @return The right content inset for this toolbar 1188 * 1189 * @see #setContentInsetsRelative(int, int) 1190 * @see #setContentInsetsAbsolute(int, int) 1191 * @see #getContentInsetStart() 1192 * @see #getContentInsetEnd() 1193 * @see #getContentInsetLeft() 1194 * @attr ref android.R.styleable#Toolbar_contentInsetRight 1195 */ 1196 public int getContentInsetRight() { 1197 return mContentInsets.getRight(); 1198 } 1199 1200 /** 1201 * Gets the start content inset to use when a navigation button is present. 1202 * 1203 * <p>Different content insets are often called for when additional buttons are present 1204 * in the toolbar, as well as at different toolbar sizes. The larger value of 1205 * {@link #getContentInsetStart()} and this value will be used during layout.</p> 1206 * 1207 * @return the start content inset used when a navigation icon has been set in pixels 1208 * 1209 * @see #setContentInsetStartWithNavigation(int) 1210 * @attr ref android.R.styleable#Toolbar_contentInsetStartWithNavigation 1211 */ 1212 public int getContentInsetStartWithNavigation() { 1213 return mContentInsetStartWithNavigation != RtlSpacingHelper.UNDEFINED 1214 ? mContentInsetStartWithNavigation 1215 : getContentInsetStart(); 1216 } 1217 1218 /** 1219 * Sets the start content inset to use when a navigation button is present. 1220 * 1221 * <p>Different content insets are often called for when additional buttons are present 1222 * in the toolbar, as well as at different toolbar sizes. The larger value of 1223 * {@link #getContentInsetStart()} and this value will be used during layout.</p> 1224 * 1225 * @param insetStartWithNavigation the inset to use when a navigation icon has been set 1226 * in pixels 1227 * 1228 * @see #getContentInsetStartWithNavigation() 1229 * @attr ref android.R.styleable#Toolbar_contentInsetStartWithNavigation 1230 */ 1231 public void setContentInsetStartWithNavigation(int insetStartWithNavigation) { 1232 if (insetStartWithNavigation < 0) { 1233 insetStartWithNavigation = RtlSpacingHelper.UNDEFINED; 1234 } 1235 if (insetStartWithNavigation != mContentInsetStartWithNavigation) { 1236 mContentInsetStartWithNavigation = insetStartWithNavigation; 1237 if (getNavigationIcon() != null) { 1238 requestLayout(); 1239 } 1240 } 1241 } 1242 1243 /** 1244 * Gets the end content inset to use when action buttons are present. 1245 * 1246 * <p>Different content insets are often called for when additional buttons are present 1247 * in the toolbar, as well as at different toolbar sizes. The larger value of 1248 * {@link #getContentInsetEnd()} and this value will be used during layout.</p> 1249 * 1250 * @return the end content inset used when a menu has been set in pixels 1251 * 1252 * @see #setContentInsetEndWithActions(int) 1253 * @attr ref android.R.styleable#Toolbar_contentInsetEndWithActions 1254 */ 1255 public int getContentInsetEndWithActions() { 1256 return mContentInsetEndWithActions != RtlSpacingHelper.UNDEFINED 1257 ? mContentInsetEndWithActions 1258 : getContentInsetEnd(); 1259 } 1260 1261 /** 1262 * Sets the start content inset to use when action buttons are present. 1263 * 1264 * <p>Different content insets are often called for when additional buttons are present 1265 * in the toolbar, as well as at different toolbar sizes. The larger value of 1266 * {@link #getContentInsetEnd()} and this value will be used during layout.</p> 1267 * 1268 * @param insetEndWithActions the inset to use when a menu has been set in pixels 1269 * 1270 * @see #setContentInsetEndWithActions(int) 1271 * @attr ref android.R.styleable#Toolbar_contentInsetEndWithActions 1272 */ 1273 public void setContentInsetEndWithActions(int insetEndWithActions) { 1274 if (insetEndWithActions < 0) { 1275 insetEndWithActions = RtlSpacingHelper.UNDEFINED; 1276 } 1277 if (insetEndWithActions != mContentInsetEndWithActions) { 1278 mContentInsetEndWithActions = insetEndWithActions; 1279 if (getNavigationIcon() != null) { 1280 requestLayout(); 1281 } 1282 } 1283 } 1284 1285 /** 1286 * Gets the content inset that will be used on the starting side of the bar in the current 1287 * toolbar configuration. 1288 * 1289 * @return the current content inset start in pixels 1290 * 1291 * @see #getContentInsetStartWithNavigation() 1292 */ 1293 public int getCurrentContentInsetStart() { 1294 return getNavigationIcon() != null 1295 ? Math.max(getContentInsetStart(), Math.max(mContentInsetStartWithNavigation, 0)) 1296 : getContentInsetStart(); 1297 } 1298 1299 /** 1300 * Gets the content inset that will be used on the ending side of the bar in the current 1301 * toolbar configuration. 1302 * 1303 * @return the current content inset end in pixels 1304 * 1305 * @see #getContentInsetEndWithActions() 1306 */ 1307 public int getCurrentContentInsetEnd() { 1308 boolean hasActions = false; 1309 if (mMenuView != null) { 1310 final MenuBuilder mb = mMenuView.peekMenu(); 1311 hasActions = mb != null && mb.hasVisibleItems(); 1312 } 1313 return hasActions 1314 ? Math.max(getContentInsetEnd(), Math.max(mContentInsetEndWithActions, 0)) 1315 : getContentInsetEnd(); 1316 } 1317 1318 /** 1319 * Gets the content inset that will be used on the left side of the bar in the current 1320 * toolbar configuration. 1321 * 1322 * @return the current content inset left in pixels 1323 * 1324 * @see #getContentInsetStartWithNavigation() 1325 * @see #getContentInsetEndWithActions() 1326 */ 1327 public int getCurrentContentInsetLeft() { 1328 return isLayoutRtl() 1329 ? getCurrentContentInsetEnd() 1330 : getCurrentContentInsetStart(); 1331 } 1332 1333 /** 1334 * Gets the content inset that will be used on the right side of the bar in the current 1335 * toolbar configuration. 1336 * 1337 * @return the current content inset right in pixels 1338 * 1339 * @see #getContentInsetStartWithNavigation() 1340 * @see #getContentInsetEndWithActions() 1341 */ 1342 public int getCurrentContentInsetRight() { 1343 return isLayoutRtl() 1344 ? getCurrentContentInsetStart() 1345 : getCurrentContentInsetEnd(); 1346 } 1347 1348 private void ensureNavButtonView() { 1349 if (mNavButtonView == null) { 1350 mNavButtonView = new ImageButton(getContext(), null, 0, mNavButtonStyle); 1351 final LayoutParams lp = generateDefaultLayoutParams(); 1352 lp.gravity = Gravity.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK); 1353 mNavButtonView.setLayoutParams(lp); 1354 } 1355 } 1356 1357 private void ensureCollapseButtonView() { 1358 if (mCollapseButtonView == null) { 1359 mCollapseButtonView = new ImageButton(getContext(), null, 0, mNavButtonStyle); 1360 mCollapseButtonView.setImageDrawable(mCollapseIcon); 1361 mCollapseButtonView.setContentDescription(mCollapseDescription); 1362 final LayoutParams lp = generateDefaultLayoutParams(); 1363 lp.gravity = Gravity.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK); 1364 lp.mViewType = LayoutParams.EXPANDED; 1365 mCollapseButtonView.setLayoutParams(lp); 1366 mCollapseButtonView.setOnClickListener(new OnClickListener() { 1367 @Override 1368 public void onClick(View v) { 1369 collapseActionView(); 1370 } 1371 }); 1372 } 1373 } 1374 1375 private void addSystemView(View v, boolean allowHide) { 1376 final ViewGroup.LayoutParams vlp = v.getLayoutParams(); 1377 final LayoutParams lp; 1378 if (vlp == null) { 1379 lp = generateDefaultLayoutParams(); 1380 } else if (!checkLayoutParams(vlp)) { 1381 lp = generateLayoutParams(vlp); 1382 } else { 1383 lp = (LayoutParams) vlp; 1384 } 1385 lp.mViewType = LayoutParams.SYSTEM; 1386 1387 if (allowHide && mExpandedActionView != null) { 1388 v.setLayoutParams(lp); 1389 mHiddenViews.add(v); 1390 } else { 1391 addView(v, lp); 1392 } 1393 } 1394 1395 @Override 1396 protected Parcelable onSaveInstanceState() { 1397 SavedState state = new SavedState(super.onSaveInstanceState()); 1398 1399 if (mExpandedMenuPresenter != null && mExpandedMenuPresenter.mCurrentExpandedItem != null) { 1400 state.expandedMenuItemId = mExpandedMenuPresenter.mCurrentExpandedItem.getItemId(); 1401 } 1402 1403 state.isOverflowOpen = isOverflowMenuShowing(); 1404 1405 return state; 1406 } 1407 1408 @Override 1409 protected void onRestoreInstanceState(Parcelable state) { 1410 final SavedState ss = (SavedState) state; 1411 super.onRestoreInstanceState(ss.getSuperState()); 1412 1413 final Menu menu = mMenuView != null ? mMenuView.peekMenu() : null; 1414 if (ss.expandedMenuItemId != 0 && mExpandedMenuPresenter != null && menu != null) { 1415 final MenuItem item = menu.findItem(ss.expandedMenuItemId); 1416 if (item != null) { 1417 item.expandActionView(); 1418 } 1419 } 1420 1421 if (ss.isOverflowOpen) { 1422 postShowOverflowMenu(); 1423 } 1424 } 1425 1426 private void postShowOverflowMenu() { 1427 removeCallbacks(mShowOverflowMenuRunnable); 1428 post(mShowOverflowMenuRunnable); 1429 } 1430 1431 @Override 1432 protected void onDetachedFromWindow() { 1433 super.onDetachedFromWindow(); 1434 removeCallbacks(mShowOverflowMenuRunnable); 1435 } 1436 1437 @Override 1438 public boolean onTouchEvent(MotionEvent ev) { 1439 // Toolbars always eat touch events, but should still respect the touch event dispatch 1440 // contract. If the normal View implementation doesn't want the events, we'll just silently 1441 // eat the rest of the gesture without reporting the events to the default implementation 1442 // since that's what it expects. 1443 1444 final int action = ev.getActionMasked(); 1445 if (action == MotionEvent.ACTION_DOWN) { 1446 mEatingTouch = false; 1447 } 1448 1449 if (!mEatingTouch) { 1450 final boolean handled = super.onTouchEvent(ev); 1451 if (action == MotionEvent.ACTION_DOWN && !handled) { 1452 mEatingTouch = true; 1453 } 1454 } 1455 1456 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { 1457 mEatingTouch = false; 1458 } 1459 1460 return true; 1461 } 1462 1463 /** 1464 * @hide 1465 */ 1466 @Override 1467 protected void onSetLayoutParams(View child, ViewGroup.LayoutParams lp) { 1468 /* 1469 * Apps may set ActionBar.LayoutParams on their action bar custom views when 1470 * a Toolbar is actually acting in the role of the action bar. Perform a quick 1471 * switch with Toolbar.LayoutParams whenever this happens. This does leave open 1472 * one potential gotcha: if an app retains the ActionBar.LayoutParams reference 1473 * and attempts to keep making changes to it before layout those changes won't 1474 * be reflected in the final results. 1475 */ 1476 if (!checkLayoutParams(lp)) { 1477 child.setLayoutParams(generateLayoutParams(lp)); 1478 } 1479 } 1480 1481 private void measureChildConstrained(View child, int parentWidthSpec, int widthUsed, 1482 int parentHeightSpec, int heightUsed, int heightConstraint) { 1483 final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); 1484 1485 int childWidthSpec = getChildMeasureSpec(parentWidthSpec, 1486 mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin 1487 + widthUsed, lp.width); 1488 int childHeightSpec = getChildMeasureSpec(parentHeightSpec, 1489 mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin 1490 + heightUsed, lp.height); 1491 1492 final int childHeightMode = MeasureSpec.getMode(childHeightSpec); 1493 if (childHeightMode != MeasureSpec.EXACTLY && heightConstraint >= 0) { 1494 final int size = childHeightMode != MeasureSpec.UNSPECIFIED ? 1495 Math.min(MeasureSpec.getSize(childHeightSpec), heightConstraint) : 1496 heightConstraint; 1497 childHeightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); 1498 } 1499 child.measure(childWidthSpec, childHeightSpec); 1500 } 1501 1502 /** 1503 * Returns the width + uncollapsed margins 1504 */ 1505 private int measureChildCollapseMargins(View child, 1506 int parentWidthMeasureSpec, int widthUsed, 1507 int parentHeightMeasureSpec, int heightUsed, int[] collapsingMargins) { 1508 final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); 1509 1510 final int leftDiff = lp.leftMargin - collapsingMargins[0]; 1511 final int rightDiff = lp.rightMargin - collapsingMargins[1]; 1512 final int leftMargin = Math.max(0, leftDiff); 1513 final int rightMargin = Math.max(0, rightDiff); 1514 final int hMargins = leftMargin + rightMargin; 1515 collapsingMargins[0] = Math.max(0, -leftDiff); 1516 collapsingMargins[1] = Math.max(0, -rightDiff); 1517 1518 final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, 1519 mPaddingLeft + mPaddingRight + hMargins + widthUsed, lp.width); 1520 final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, 1521 mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin 1522 + heightUsed, lp.height); 1523 1524 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 1525 return child.getMeasuredWidth() + hMargins; 1526 } 1527 1528 /** 1529 * Returns true if the Toolbar is collapsible and has no child views with a measured size > 0. 1530 */ 1531 private boolean shouldCollapse() { 1532 if (!mCollapsible) return false; 1533 1534 final int childCount = getChildCount(); 1535 for (int i = 0; i < childCount; i++) { 1536 final View child = getChildAt(i); 1537 if (shouldLayout(child) && child.getMeasuredWidth() > 0 && 1538 child.getMeasuredHeight() > 0) { 1539 return false; 1540 } 1541 } 1542 return true; 1543 } 1544 1545 @Override 1546 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1547 int width = 0; 1548 int height = 0; 1549 int childState = 0; 1550 1551 final int[] collapsingMargins = mTempMargins; 1552 final int marginStartIndex; 1553 final int marginEndIndex; 1554 if (isLayoutRtl()) { 1555 marginStartIndex = 1; 1556 marginEndIndex = 0; 1557 } else { 1558 marginStartIndex = 0; 1559 marginEndIndex = 1; 1560 } 1561 1562 // System views measure first. 1563 1564 int navWidth = 0; 1565 if (shouldLayout(mNavButtonView)) { 1566 measureChildConstrained(mNavButtonView, widthMeasureSpec, width, heightMeasureSpec, 0, 1567 mMaxButtonHeight); 1568 navWidth = mNavButtonView.getMeasuredWidth() + getHorizontalMargins(mNavButtonView); 1569 height = Math.max(height, mNavButtonView.getMeasuredHeight() + 1570 getVerticalMargins(mNavButtonView)); 1571 childState = combineMeasuredStates(childState, mNavButtonView.getMeasuredState()); 1572 } 1573 1574 if (shouldLayout(mCollapseButtonView)) { 1575 measureChildConstrained(mCollapseButtonView, widthMeasureSpec, width, 1576 heightMeasureSpec, 0, mMaxButtonHeight); 1577 navWidth = mCollapseButtonView.getMeasuredWidth() + 1578 getHorizontalMargins(mCollapseButtonView); 1579 height = Math.max(height, mCollapseButtonView.getMeasuredHeight() + 1580 getVerticalMargins(mCollapseButtonView)); 1581 childState = combineMeasuredStates(childState, mCollapseButtonView.getMeasuredState()); 1582 } 1583 1584 final int contentInsetStart = getCurrentContentInsetStart(); 1585 width += Math.max(contentInsetStart, navWidth); 1586 collapsingMargins[marginStartIndex] = Math.max(0, contentInsetStart - navWidth); 1587 1588 int menuWidth = 0; 1589 if (shouldLayout(mMenuView)) { 1590 measureChildConstrained(mMenuView, widthMeasureSpec, width, heightMeasureSpec, 0, 1591 mMaxButtonHeight); 1592 menuWidth = mMenuView.getMeasuredWidth() + getHorizontalMargins(mMenuView); 1593 height = Math.max(height, mMenuView.getMeasuredHeight() + 1594 getVerticalMargins(mMenuView)); 1595 childState = combineMeasuredStates(childState, mMenuView.getMeasuredState()); 1596 } 1597 1598 final int contentInsetEnd = getCurrentContentInsetEnd(); 1599 width += Math.max(contentInsetEnd, menuWidth); 1600 collapsingMargins[marginEndIndex] = Math.max(0, contentInsetEnd - menuWidth); 1601 1602 if (shouldLayout(mExpandedActionView)) { 1603 width += measureChildCollapseMargins(mExpandedActionView, widthMeasureSpec, width, 1604 heightMeasureSpec, 0, collapsingMargins); 1605 height = Math.max(height, mExpandedActionView.getMeasuredHeight() + 1606 getVerticalMargins(mExpandedActionView)); 1607 childState = combineMeasuredStates(childState, mExpandedActionView.getMeasuredState()); 1608 } 1609 1610 if (shouldLayout(mLogoView)) { 1611 width += measureChildCollapseMargins(mLogoView, widthMeasureSpec, width, 1612 heightMeasureSpec, 0, collapsingMargins); 1613 height = Math.max(height, mLogoView.getMeasuredHeight() + 1614 getVerticalMargins(mLogoView)); 1615 childState = combineMeasuredStates(childState, mLogoView.getMeasuredState()); 1616 } 1617 1618 final int childCount = getChildCount(); 1619 for (int i = 0; i < childCount; i++) { 1620 final View child = getChildAt(i); 1621 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1622 if (lp.mViewType != LayoutParams.CUSTOM || !shouldLayout(child)) { 1623 // We already got all system views above. Skip them and GONE views. 1624 continue; 1625 } 1626 1627 width += measureChildCollapseMargins(child, widthMeasureSpec, width, 1628 heightMeasureSpec, 0, collapsingMargins); 1629 height = Math.max(height, child.getMeasuredHeight() + getVerticalMargins(child)); 1630 childState = combineMeasuredStates(childState, child.getMeasuredState()); 1631 } 1632 1633 int titleWidth = 0; 1634 int titleHeight = 0; 1635 final int titleVertMargins = mTitleMarginTop + mTitleMarginBottom; 1636 final int titleHorizMargins = mTitleMarginStart + mTitleMarginEnd; 1637 if (shouldLayout(mTitleTextView)) { 1638 titleWidth = measureChildCollapseMargins(mTitleTextView, widthMeasureSpec, 1639 width + titleHorizMargins, heightMeasureSpec, titleVertMargins, 1640 collapsingMargins); 1641 titleWidth = mTitleTextView.getMeasuredWidth() + getHorizontalMargins(mTitleTextView); 1642 titleHeight = mTitleTextView.getMeasuredHeight() + getVerticalMargins(mTitleTextView); 1643 childState = combineMeasuredStates(childState, mTitleTextView.getMeasuredState()); 1644 } 1645 if (shouldLayout(mSubtitleTextView)) { 1646 titleWidth = Math.max(titleWidth, measureChildCollapseMargins(mSubtitleTextView, 1647 widthMeasureSpec, width + titleHorizMargins, 1648 heightMeasureSpec, titleHeight + titleVertMargins, 1649 collapsingMargins)); 1650 titleHeight += mSubtitleTextView.getMeasuredHeight() + 1651 getVerticalMargins(mSubtitleTextView); 1652 childState = combineMeasuredStates(childState, mSubtitleTextView.getMeasuredState()); 1653 } 1654 1655 width += titleWidth; 1656 height = Math.max(height, titleHeight); 1657 1658 // Measurement already took padding into account for available space for the children, 1659 // add it in for the final size. 1660 width += getPaddingLeft() + getPaddingRight(); 1661 height += getPaddingTop() + getPaddingBottom(); 1662 1663 final int measuredWidth = resolveSizeAndState( 1664 Math.max(width, getSuggestedMinimumWidth()), 1665 widthMeasureSpec, childState & MEASURED_STATE_MASK); 1666 final int measuredHeight = resolveSizeAndState( 1667 Math.max(height, getSuggestedMinimumHeight()), 1668 heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT); 1669 1670 setMeasuredDimension(measuredWidth, shouldCollapse() ? 0 : measuredHeight); 1671 } 1672 1673 @Override 1674 protected void onLayout(boolean changed, int l, int t, int r, int b) { 1675 final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; 1676 final int width = getWidth(); 1677 final int height = getHeight(); 1678 final int paddingLeft = getPaddingLeft(); 1679 final int paddingRight = getPaddingRight(); 1680 final int paddingTop = getPaddingTop(); 1681 final int paddingBottom = getPaddingBottom(); 1682 int left = paddingLeft; 1683 int right = width - paddingRight; 1684 1685 final int[] collapsingMargins = mTempMargins; 1686 collapsingMargins[0] = collapsingMargins[1] = 0; 1687 1688 // Align views within the minimum toolbar height, if set. 1689 final int alignmentHeight = getMinimumHeight(); 1690 1691 if (shouldLayout(mNavButtonView)) { 1692 if (isRtl) { 1693 right = layoutChildRight(mNavButtonView, right, collapsingMargins, 1694 alignmentHeight); 1695 } else { 1696 left = layoutChildLeft(mNavButtonView, left, collapsingMargins, 1697 alignmentHeight); 1698 } 1699 } 1700 1701 if (shouldLayout(mCollapseButtonView)) { 1702 if (isRtl) { 1703 right = layoutChildRight(mCollapseButtonView, right, collapsingMargins, 1704 alignmentHeight); 1705 } else { 1706 left = layoutChildLeft(mCollapseButtonView, left, collapsingMargins, 1707 alignmentHeight); 1708 } 1709 } 1710 1711 if (shouldLayout(mMenuView)) { 1712 if (isRtl) { 1713 left = layoutChildLeft(mMenuView, left, collapsingMargins, 1714 alignmentHeight); 1715 } else { 1716 right = layoutChildRight(mMenuView, right, collapsingMargins, 1717 alignmentHeight); 1718 } 1719 } 1720 1721 final int contentInsetLeft = getCurrentContentInsetLeft(); 1722 final int contentInsetRight = getCurrentContentInsetRight(); 1723 collapsingMargins[0] = Math.max(0, contentInsetLeft - left); 1724 collapsingMargins[1] = Math.max(0, contentInsetRight - (width - paddingRight - right)); 1725 left = Math.max(left, contentInsetLeft); 1726 right = Math.min(right, width - paddingRight - contentInsetRight); 1727 1728 if (shouldLayout(mExpandedActionView)) { 1729 if (isRtl) { 1730 right = layoutChildRight(mExpandedActionView, right, collapsingMargins, 1731 alignmentHeight); 1732 } else { 1733 left = layoutChildLeft(mExpandedActionView, left, collapsingMargins, 1734 alignmentHeight); 1735 } 1736 } 1737 1738 if (shouldLayout(mLogoView)) { 1739 if (isRtl) { 1740 right = layoutChildRight(mLogoView, right, collapsingMargins, 1741 alignmentHeight); 1742 } else { 1743 left = layoutChildLeft(mLogoView, left, collapsingMargins, 1744 alignmentHeight); 1745 } 1746 } 1747 1748 final boolean layoutTitle = shouldLayout(mTitleTextView); 1749 final boolean layoutSubtitle = shouldLayout(mSubtitleTextView); 1750 int titleHeight = 0; 1751 if (layoutTitle) { 1752 final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams(); 1753 titleHeight += lp.topMargin + mTitleTextView.getMeasuredHeight() + lp.bottomMargin; 1754 } 1755 if (layoutSubtitle) { 1756 final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams(); 1757 titleHeight += lp.topMargin + mSubtitleTextView.getMeasuredHeight() + lp.bottomMargin; 1758 } 1759 1760 if (layoutTitle || layoutSubtitle) { 1761 int titleTop; 1762 final View topChild = layoutTitle ? mTitleTextView : mSubtitleTextView; 1763 final View bottomChild = layoutSubtitle ? mSubtitleTextView : mTitleTextView; 1764 final LayoutParams toplp = (LayoutParams) topChild.getLayoutParams(); 1765 final LayoutParams bottomlp = (LayoutParams) bottomChild.getLayoutParams(); 1766 final boolean titleHasWidth = layoutTitle && mTitleTextView.getMeasuredWidth() > 0 1767 || layoutSubtitle && mSubtitleTextView.getMeasuredWidth() > 0; 1768 1769 switch (mGravity & Gravity.VERTICAL_GRAVITY_MASK) { 1770 case Gravity.TOP: 1771 titleTop = getPaddingTop() + toplp.topMargin + mTitleMarginTop; 1772 break; 1773 default: 1774 case Gravity.CENTER_VERTICAL: 1775 final int space = height - paddingTop - paddingBottom; 1776 int spaceAbove = (space - titleHeight) / 2; 1777 if (spaceAbove < toplp.topMargin + mTitleMarginTop) { 1778 spaceAbove = toplp.topMargin + mTitleMarginTop; 1779 } else { 1780 final int spaceBelow = height - paddingBottom - titleHeight - 1781 spaceAbove - paddingTop; 1782 if (spaceBelow < toplp.bottomMargin + mTitleMarginBottom) { 1783 spaceAbove = Math.max(0, spaceAbove - 1784 (bottomlp.bottomMargin + mTitleMarginBottom - spaceBelow)); 1785 } 1786 } 1787 titleTop = paddingTop + spaceAbove; 1788 break; 1789 case Gravity.BOTTOM: 1790 titleTop = height - paddingBottom - bottomlp.bottomMargin - mTitleMarginBottom - 1791 titleHeight; 1792 break; 1793 } 1794 if (isRtl) { 1795 final int rd = (titleHasWidth ? mTitleMarginStart : 0) - collapsingMargins[1]; 1796 right -= Math.max(0, rd); 1797 collapsingMargins[1] = Math.max(0, -rd); 1798 int titleRight = right; 1799 int subtitleRight = right; 1800 1801 if (layoutTitle) { 1802 final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams(); 1803 final int titleLeft = titleRight - mTitleTextView.getMeasuredWidth(); 1804 final int titleBottom = titleTop + mTitleTextView.getMeasuredHeight(); 1805 mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom); 1806 titleRight = titleLeft - mTitleMarginEnd; 1807 titleTop = titleBottom + lp.bottomMargin; 1808 } 1809 if (layoutSubtitle) { 1810 final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams(); 1811 titleTop += lp.topMargin; 1812 final int subtitleLeft = subtitleRight - mSubtitleTextView.getMeasuredWidth(); 1813 final int subtitleBottom = titleTop + mSubtitleTextView.getMeasuredHeight(); 1814 mSubtitleTextView.layout(subtitleLeft, titleTop, subtitleRight, subtitleBottom); 1815 subtitleRight = subtitleRight - mTitleMarginEnd; 1816 titleTop = subtitleBottom + lp.bottomMargin; 1817 } 1818 if (titleHasWidth) { 1819 right = Math.min(titleRight, subtitleRight); 1820 } 1821 } else { 1822 final int ld = (titleHasWidth ? mTitleMarginStart : 0) - collapsingMargins[0]; 1823 left += Math.max(0, ld); 1824 collapsingMargins[0] = Math.max(0, -ld); 1825 int titleLeft = left; 1826 int subtitleLeft = left; 1827 1828 if (layoutTitle) { 1829 final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams(); 1830 final int titleRight = titleLeft + mTitleTextView.getMeasuredWidth(); 1831 final int titleBottom = titleTop + mTitleTextView.getMeasuredHeight(); 1832 mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom); 1833 titleLeft = titleRight + mTitleMarginEnd; 1834 titleTop = titleBottom + lp.bottomMargin; 1835 } 1836 if (layoutSubtitle) { 1837 final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams(); 1838 titleTop += lp.topMargin; 1839 final int subtitleRight = subtitleLeft + mSubtitleTextView.getMeasuredWidth(); 1840 final int subtitleBottom = titleTop + mSubtitleTextView.getMeasuredHeight(); 1841 mSubtitleTextView.layout(subtitleLeft, titleTop, subtitleRight, subtitleBottom); 1842 subtitleLeft = subtitleRight + mTitleMarginEnd; 1843 titleTop = subtitleBottom + lp.bottomMargin; 1844 } 1845 if (titleHasWidth) { 1846 left = Math.max(titleLeft, subtitleLeft); 1847 } 1848 } 1849 } 1850 1851 // Get all remaining children sorted for layout. This is all prepared 1852 // such that absolute layout direction can be used below. 1853 1854 addCustomViewsWithGravity(mTempViews, Gravity.LEFT); 1855 final int leftViewsCount = mTempViews.size(); 1856 for (int i = 0; i < leftViewsCount; i++) { 1857 left = layoutChildLeft(mTempViews.get(i), left, collapsingMargins, 1858 alignmentHeight); 1859 } 1860 1861 addCustomViewsWithGravity(mTempViews, Gravity.RIGHT); 1862 final int rightViewsCount = mTempViews.size(); 1863 for (int i = 0; i < rightViewsCount; i++) { 1864 right = layoutChildRight(mTempViews.get(i), right, collapsingMargins, 1865 alignmentHeight); 1866 } 1867 1868 // Centered views try to center with respect to the whole bar, but views pinned 1869 // to the left or right can push the mass of centered views to one side or the other. 1870 addCustomViewsWithGravity(mTempViews, Gravity.CENTER_HORIZONTAL); 1871 final int centerViewsWidth = getViewListMeasuredWidth(mTempViews, collapsingMargins); 1872 final int parentCenter = paddingLeft + (width - paddingLeft - paddingRight) / 2; 1873 final int halfCenterViewsWidth = centerViewsWidth / 2; 1874 int centerLeft = parentCenter - halfCenterViewsWidth; 1875 final int centerRight = centerLeft + centerViewsWidth; 1876 if (centerLeft < left) { 1877 centerLeft = left; 1878 } else if (centerRight > right) { 1879 centerLeft -= centerRight - right; 1880 } 1881 1882 final int centerViewsCount = mTempViews.size(); 1883 for (int i = 0; i < centerViewsCount; i++) { 1884 centerLeft = layoutChildLeft(mTempViews.get(i), centerLeft, collapsingMargins, 1885 alignmentHeight); 1886 } 1887 1888 mTempViews.clear(); 1889 } 1890 1891 private int getViewListMeasuredWidth(List<View> views, int[] collapsingMargins) { 1892 int collapseLeft = collapsingMargins[0]; 1893 int collapseRight = collapsingMargins[1]; 1894 int width = 0; 1895 final int count = views.size(); 1896 for (int i = 0; i < count; i++) { 1897 final View v = views.get(i); 1898 final LayoutParams lp = (LayoutParams) v.getLayoutParams(); 1899 final int l = lp.leftMargin - collapseLeft; 1900 final int r = lp.rightMargin - collapseRight; 1901 final int leftMargin = Math.max(0, l); 1902 final int rightMargin = Math.max(0, r); 1903 collapseLeft = Math.max(0, -l); 1904 collapseRight = Math.max(0, -r); 1905 width += leftMargin + v.getMeasuredWidth() + rightMargin; 1906 } 1907 return width; 1908 } 1909 1910 private int layoutChildLeft(View child, int left, int[] collapsingMargins, 1911 int alignmentHeight) { 1912 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1913 final int l = lp.leftMargin - collapsingMargins[0]; 1914 left += Math.max(0, l); 1915 collapsingMargins[0] = Math.max(0, -l); 1916 final int top = getChildTop(child, alignmentHeight); 1917 final int childWidth = child.getMeasuredWidth(); 1918 child.layout(left, top, left + childWidth, top + child.getMeasuredHeight()); 1919 left += childWidth + lp.rightMargin; 1920 return left; 1921 } 1922 1923 private int layoutChildRight(View child, int right, int[] collapsingMargins, 1924 int alignmentHeight) { 1925 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1926 final int r = lp.rightMargin - collapsingMargins[1]; 1927 right -= Math.max(0, r); 1928 collapsingMargins[1] = Math.max(0, -r); 1929 final int top = getChildTop(child, alignmentHeight); 1930 final int childWidth = child.getMeasuredWidth(); 1931 child.layout(right - childWidth, top, right, top + child.getMeasuredHeight()); 1932 right -= childWidth + lp.leftMargin; 1933 return right; 1934 } 1935 1936 private int getChildTop(View child, int alignmentHeight) { 1937 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1938 final int childHeight = child.getMeasuredHeight(); 1939 final int alignmentOffset = alignmentHeight > 0 ? (childHeight - alignmentHeight) / 2 : 0; 1940 switch (getChildVerticalGravity(lp.gravity)) { 1941 case Gravity.TOP: 1942 return getPaddingTop() - alignmentOffset; 1943 1944 case Gravity.BOTTOM: 1945 return getHeight() - getPaddingBottom() - childHeight 1946 - lp.bottomMargin - alignmentOffset; 1947 1948 default: 1949 case Gravity.CENTER_VERTICAL: 1950 final int paddingTop = getPaddingTop(); 1951 final int paddingBottom = getPaddingBottom(); 1952 final int height = getHeight(); 1953 final int space = height - paddingTop - paddingBottom; 1954 int spaceAbove = (space - childHeight) / 2; 1955 if (spaceAbove < lp.topMargin) { 1956 spaceAbove = lp.topMargin; 1957 } else { 1958 final int spaceBelow = height - paddingBottom - childHeight - 1959 spaceAbove - paddingTop; 1960 if (spaceBelow < lp.bottomMargin) { 1961 spaceAbove = Math.max(0, spaceAbove - (lp.bottomMargin - spaceBelow)); 1962 } 1963 } 1964 return paddingTop + spaceAbove; 1965 } 1966 } 1967 1968 private int getChildVerticalGravity(int gravity) { 1969 final int vgrav = gravity & Gravity.VERTICAL_GRAVITY_MASK; 1970 switch (vgrav) { 1971 case Gravity.TOP: 1972 case Gravity.BOTTOM: 1973 case Gravity.CENTER_VERTICAL: 1974 return vgrav; 1975 default: 1976 return mGravity & Gravity.VERTICAL_GRAVITY_MASK; 1977 } 1978 } 1979 1980 /** 1981 * Prepare a list of non-SYSTEM child views. If the layout direction is RTL 1982 * this will be in reverse child order. 1983 * 1984 * @param views List to populate. It will be cleared before use. 1985 * @param gravity Horizontal gravity to match against 1986 */ 1987 private void addCustomViewsWithGravity(List<View> views, int gravity) { 1988 final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; 1989 final int childCount = getChildCount(); 1990 final int absGrav = Gravity.getAbsoluteGravity(gravity, getLayoutDirection()); 1991 1992 views.clear(); 1993 1994 if (isRtl) { 1995 for (int i = childCount - 1; i >= 0; i--) { 1996 final View child = getChildAt(i); 1997 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1998 if (lp.mViewType == LayoutParams.CUSTOM && shouldLayout(child) && 1999 getChildHorizontalGravity(lp.gravity) == absGrav) { 2000 views.add(child); 2001 } 2002 } 2003 } else { 2004 for (int i = 0; i < childCount; i++) { 2005 final View child = getChildAt(i); 2006 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 2007 if (lp.mViewType == LayoutParams.CUSTOM && shouldLayout(child) && 2008 getChildHorizontalGravity(lp.gravity) == absGrav) { 2009 views.add(child); 2010 } 2011 } 2012 } 2013 } 2014 2015 private int getChildHorizontalGravity(int gravity) { 2016 final int ld = getLayoutDirection(); 2017 final int absGrav = Gravity.getAbsoluteGravity(gravity, ld); 2018 final int hGrav = absGrav & Gravity.HORIZONTAL_GRAVITY_MASK; 2019 switch (hGrav) { 2020 case Gravity.LEFT: 2021 case Gravity.RIGHT: 2022 case Gravity.CENTER_HORIZONTAL: 2023 return hGrav; 2024 default: 2025 return ld == LAYOUT_DIRECTION_RTL ? Gravity.RIGHT : Gravity.LEFT; 2026 } 2027 } 2028 2029 private boolean shouldLayout(View view) { 2030 return view != null && view.getParent() == this && view.getVisibility() != GONE; 2031 } 2032 2033 private int getHorizontalMargins(View v) { 2034 final MarginLayoutParams mlp = (MarginLayoutParams) v.getLayoutParams(); 2035 return mlp.getMarginStart() + mlp.getMarginEnd(); 2036 } 2037 2038 private int getVerticalMargins(View v) { 2039 final MarginLayoutParams mlp = (MarginLayoutParams) v.getLayoutParams(); 2040 return mlp.topMargin + mlp.bottomMargin; 2041 } 2042 2043 @Override 2044 public LayoutParams generateLayoutParams(AttributeSet attrs) { 2045 return new LayoutParams(getContext(), attrs); 2046 } 2047 2048 @Override 2049 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 2050 if (p instanceof LayoutParams) { 2051 return new LayoutParams((LayoutParams) p); 2052 } else if (p instanceof ActionBar.LayoutParams) { 2053 return new LayoutParams((ActionBar.LayoutParams) p); 2054 } else if (p instanceof MarginLayoutParams) { 2055 return new LayoutParams((MarginLayoutParams) p); 2056 } else { 2057 return new LayoutParams(p); 2058 } 2059 } 2060 2061 @Override 2062 protected LayoutParams generateDefaultLayoutParams() { 2063 return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 2064 } 2065 2066 @Override 2067 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 2068 return super.checkLayoutParams(p) && p instanceof LayoutParams; 2069 } 2070 2071 private static boolean isCustomView(View child) { 2072 return ((LayoutParams) child.getLayoutParams()).mViewType == LayoutParams.CUSTOM; 2073 } 2074 2075 /** @hide */ 2076 public DecorToolbar getWrapper() { 2077 if (mWrapper == null) { 2078 mWrapper = new ToolbarWidgetWrapper(this, true); 2079 } 2080 return mWrapper; 2081 } 2082 2083 void removeChildrenForExpandedActionView() { 2084 final int childCount = getChildCount(); 2085 // Go backwards since we're removing from the list 2086 for (int i = childCount - 1; i >= 0; i--) { 2087 final View child = getChildAt(i); 2088 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 2089 if (lp.mViewType != LayoutParams.EXPANDED && child != mMenuView) { 2090 removeViewAt(i); 2091 mHiddenViews.add(child); 2092 } 2093 } 2094 } 2095 2096 void addChildrenForExpandedActionView() { 2097 final int count = mHiddenViews.size(); 2098 // Re-add in reverse order since we removed in reverse order 2099 for (int i = count - 1; i >= 0; i--) { 2100 addView(mHiddenViews.get(i)); 2101 } 2102 mHiddenViews.clear(); 2103 } 2104 2105 private boolean isChildOrHidden(View child) { 2106 return child.getParent() == this || mHiddenViews.contains(child); 2107 } 2108 2109 /** 2110 * Force the toolbar to collapse to zero-height during measurement if 2111 * it could be considered "empty" (no visible elements with nonzero measured size) 2112 * @hide 2113 */ 2114 public void setCollapsible(boolean collapsible) { 2115 mCollapsible = collapsible; 2116 requestLayout(); 2117 } 2118 2119 /** 2120 * Must be called before the menu is accessed 2121 * @hide 2122 */ 2123 public void setMenuCallbacks(MenuPresenter.Callback pcb, MenuBuilder.Callback mcb) { 2124 mActionMenuPresenterCallback = pcb; 2125 mMenuBuilderCallback = mcb; 2126 if (mMenuView != null) { 2127 mMenuView.setMenuCallbacks(pcb, mcb); 2128 } 2129 } 2130 2131 /** 2132 * Accessor to enable LayoutLib to get ActionMenuPresenter directly. 2133 */ 2134 ActionMenuPresenter getOuterActionMenuPresenter() { 2135 return mOuterActionMenuPresenter; 2136 } 2137 2138 Context getPopupContext() { 2139 return mPopupContext; 2140 } 2141 2142 /** 2143 * Interface responsible for receiving menu item click events if the items themselves 2144 * do not have individual item click listeners. 2145 */ 2146 public interface OnMenuItemClickListener { 2147 /** 2148 * This method will be invoked when a menu item is clicked if the item itself did 2149 * not already handle the event. 2150 * 2151 * @param item {@link MenuItem} that was clicked 2152 * @return <code>true</code> if the event was handled, <code>false</code> otherwise. 2153 */ 2154 public boolean onMenuItemClick(MenuItem item); 2155 } 2156 2157 /** 2158 * Layout information for child views of Toolbars. 2159 * 2160 * <p>Toolbar.LayoutParams extends ActionBar.LayoutParams for compatibility with existing 2161 * ActionBar API. See {@link android.app.Activity#setActionBar(Toolbar) Activity.setActionBar} 2162 * for more info on how to use a Toolbar as your Activity's ActionBar.</p> 2163 * 2164 * @attr ref android.R.styleable#Toolbar_LayoutParams_layout_gravity 2165 */ 2166 public static class LayoutParams extends ActionBar.LayoutParams { 2167 static final int CUSTOM = 0; 2168 static final int SYSTEM = 1; 2169 static final int EXPANDED = 2; 2170 2171 int mViewType = CUSTOM; 2172 2173 public LayoutParams(@NonNull Context c, AttributeSet attrs) { 2174 super(c, attrs); 2175 } 2176 2177 public LayoutParams(int width, int height) { 2178 super(width, height); 2179 this.gravity = Gravity.CENTER_VERTICAL | Gravity.START; 2180 } 2181 2182 public LayoutParams(int width, int height, int gravity) { 2183 super(width, height); 2184 this.gravity = gravity; 2185 } 2186 2187 public LayoutParams(int gravity) { 2188 this(WRAP_CONTENT, MATCH_PARENT, gravity); 2189 } 2190 2191 public LayoutParams(LayoutParams source) { 2192 super(source); 2193 2194 mViewType = source.mViewType; 2195 } 2196 2197 public LayoutParams(ActionBar.LayoutParams source) { 2198 super(source); 2199 } 2200 2201 public LayoutParams(MarginLayoutParams source) { 2202 super(source); 2203 // ActionBar.LayoutParams doesn't have a MarginLayoutParams constructor. 2204 // Fake it here and copy over the relevant data. 2205 copyMarginsFrom(source); 2206 } 2207 2208 public LayoutParams(ViewGroup.LayoutParams source) { 2209 super(source); 2210 } 2211 } 2212 2213 static class SavedState extends BaseSavedState { 2214 public int expandedMenuItemId; 2215 public boolean isOverflowOpen; 2216 2217 public SavedState(Parcel source) { 2218 super(source); 2219 expandedMenuItemId = source.readInt(); 2220 isOverflowOpen = source.readInt() != 0; 2221 } 2222 2223 public SavedState(Parcelable superState) { 2224 super(superState); 2225 } 2226 2227 @Override 2228 public void writeToParcel(Parcel out, int flags) { 2229 super.writeToParcel(out, flags); 2230 out.writeInt(expandedMenuItemId); 2231 out.writeInt(isOverflowOpen ? 1 : 0); 2232 } 2233 2234 public static final Creator<SavedState> CREATOR = new Creator<SavedState>() { 2235 2236 @Override 2237 public SavedState createFromParcel(Parcel source) { 2238 return new SavedState(source); 2239 } 2240 2241 @Override 2242 public SavedState[] newArray(int size) { 2243 return new SavedState[size]; 2244 } 2245 }; 2246 } 2247 2248 private class ExpandedActionViewMenuPresenter implements MenuPresenter { 2249 MenuBuilder mMenu; 2250 MenuItemImpl mCurrentExpandedItem; 2251 2252 @Override 2253 public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) { 2254 // Clear the expanded action view when menus change. 2255 if (mMenu != null && mCurrentExpandedItem != null) { 2256 mMenu.collapseItemActionView(mCurrentExpandedItem); 2257 } 2258 mMenu = menu; 2259 } 2260 2261 @Override 2262 public MenuView getMenuView(ViewGroup root) { 2263 return null; 2264 } 2265 2266 @Override 2267 public void updateMenuView(boolean cleared) { 2268 // Make sure the expanded item we have is still there. 2269 if (mCurrentExpandedItem != null) { 2270 boolean found = false; 2271 2272 if (mMenu != null) { 2273 final int count = mMenu.size(); 2274 for (int i = 0; i < count; i++) { 2275 final MenuItem item = mMenu.getItem(i); 2276 if (item == mCurrentExpandedItem) { 2277 found = true; 2278 break; 2279 } 2280 } 2281 } 2282 2283 if (!found) { 2284 // The item we had expanded disappeared. Collapse. 2285 collapseItemActionView(mMenu, mCurrentExpandedItem); 2286 } 2287 } 2288 } 2289 2290 @Override 2291 public void setCallback(Callback cb) { 2292 } 2293 2294 @Override 2295 public boolean onSubMenuSelected(SubMenuBuilder subMenu) { 2296 return false; 2297 } 2298 2299 @Override 2300 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) { 2301 } 2302 2303 @Override 2304 public boolean flagActionItems() { 2305 return false; 2306 } 2307 2308 @Override 2309 public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) { 2310 ensureCollapseButtonView(); 2311 if (mCollapseButtonView.getParent() != Toolbar.this) { 2312 addView(mCollapseButtonView); 2313 } 2314 mExpandedActionView = item.getActionView(); 2315 mCurrentExpandedItem = item; 2316 if (mExpandedActionView.getParent() != Toolbar.this) { 2317 final LayoutParams lp = generateDefaultLayoutParams(); 2318 lp.gravity = Gravity.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK); 2319 lp.mViewType = LayoutParams.EXPANDED; 2320 mExpandedActionView.setLayoutParams(lp); 2321 addView(mExpandedActionView); 2322 } 2323 2324 removeChildrenForExpandedActionView(); 2325 requestLayout(); 2326 item.setActionViewExpanded(true); 2327 2328 if (mExpandedActionView instanceof CollapsibleActionView) { 2329 ((CollapsibleActionView) mExpandedActionView).onActionViewExpanded(); 2330 } 2331 2332 return true; 2333 } 2334 2335 @Override 2336 public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) { 2337 // Do this before detaching the actionview from the hierarchy, in case 2338 // it needs to dismiss the soft keyboard, etc. 2339 if (mExpandedActionView instanceof CollapsibleActionView) { 2340 ((CollapsibleActionView) mExpandedActionView).onActionViewCollapsed(); 2341 } 2342 2343 removeView(mExpandedActionView); 2344 removeView(mCollapseButtonView); 2345 mExpandedActionView = null; 2346 2347 addChildrenForExpandedActionView(); 2348 mCurrentExpandedItem = null; 2349 requestLayout(); 2350 item.setActionViewExpanded(false); 2351 2352 return true; 2353 } 2354 2355 @Override 2356 public int getId() { 2357 return 0; 2358 } 2359 2360 @Override 2361 public Parcelable onSaveInstanceState() { 2362 return null; 2363 } 2364 2365 @Override 2366 public void onRestoreInstanceState(Parcelable state) { 2367 } 2368 } 2369} 2370