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