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