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