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