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