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