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