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