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