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