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