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