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