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