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