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