ToolbarWidgetWrapper.java revision acf94897e0924ff5fcd3c5b371ab1aa620cb1dc6
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.support.v7.internal.widget;
19
20import android.app.ActionBar;
21import android.content.Context;
22import android.graphics.drawable.Drawable;
23import android.os.Parcelable;
24import android.support.v4.view.ViewCompat;
25import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
26import android.support.v7.appcompat.R;
27import android.support.v7.internal.app.WindowCallback;
28import android.support.v7.internal.view.menu.ActionMenuItem;
29import android.support.v7.internal.view.menu.MenuBuilder;
30import android.support.v7.internal.view.menu.MenuPresenter;
31import android.support.v7.widget.ActionMenuPresenter;
32import android.support.v7.widget.Toolbar;
33import android.text.TextUtils;
34import android.util.Log;
35import android.util.SparseArray;
36import android.view.Gravity;
37import android.view.LayoutInflater;
38import android.view.Menu;
39import android.view.View;
40import android.view.ViewGroup;
41import android.view.Window;
42import android.widget.SpinnerAdapter;
43
44/**
45 * Internal class used to interact with the Toolbar widget without
46 * exposing interface methods to the public API.
47 *
48 * <p>ToolbarWidgetWrapper manages the differences between Toolbar and ActionBarView
49 * so that either variant acting as a
50 * {@link android.support.v7.internal.app.WindowDecorActionBar WindowDecorActionBar} can behave
51 * in the same way.</p>
52 *
53 * @hide
54 */
55public class ToolbarWidgetWrapper implements DecorToolbar {
56    private static final String TAG = "ToolbarWidgetWrapper";
57
58    private static final int AFFECTS_LOGO_MASK =
59            ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_USE_LOGO;
60
61    private Toolbar mToolbar;
62
63    private int mDisplayOpts;
64    private View mTabView;
65    private SpinnerCompat mSpinner;
66    private View mCustomView;
67
68    private Drawable mIcon;
69    private Drawable mLogo;
70    private Drawable mNavIcon;
71
72    private boolean mTitleSet;
73    private CharSequence mTitle;
74    private CharSequence mSubtitle;
75    private CharSequence mHomeDescription;
76
77    private WindowCallback mWindowCallback;
78    private boolean mMenuPrepared;
79    private ActionMenuPresenter mActionMenuPresenter;
80
81    private int mNavigationMode = ActionBar.NAVIGATION_MODE_STANDARD;
82
83    private final TintManager mTintManager;
84    private int mDefaultNavigationContentDescription = 0;
85    private Drawable mDefaultNavigationIcon;
86
87    public ToolbarWidgetWrapper(Toolbar toolbar, boolean style) {
88        this(toolbar, style, R.string.abc_action_bar_up_description,
89                R.drawable.abc_ic_ab_back_mtrl_am_alpha);
90    }
91
92    public ToolbarWidgetWrapper(Toolbar toolbar, boolean style,
93            int defaultNavigationContentDescription, int defaultNavigationIcon) {
94        mToolbar = toolbar;
95        mTitle = toolbar.getTitle();
96        mSubtitle = toolbar.getSubtitle();
97        mTitleSet = mTitle != null;
98
99        if (style) {
100            final TintTypedArray a = TintTypedArray.obtainStyledAttributes(toolbar.getContext(),
101                    null, R.styleable.ActionBar, R.attr.actionBarStyle, 0);
102
103            final CharSequence title = a.getText(R.styleable.ActionBar_title);
104            if (!TextUtils.isEmpty(title)) {
105                setTitle(title);
106            }
107
108            final CharSequence subtitle = a.getText(R.styleable.ActionBar_subtitle);
109            if (!TextUtils.isEmpty(subtitle)) {
110                setSubtitle(subtitle);
111            }
112
113            final Drawable logo = a.getDrawable(R.styleable.ActionBar_logo);
114            if (logo != null) {
115                setLogo(logo);
116            }
117
118            final Drawable icon = a.getDrawable(R.styleable.ActionBar_icon);
119            if (icon != null) {
120                setIcon(icon);
121            }
122
123            final Drawable navIcon = a.getDrawable(R.styleable.ActionBar_homeAsUpIndicator);
124            if (navIcon != null) {
125                setNavigationIcon(navIcon);
126            }
127
128            setDisplayOptions(a.getInt(R.styleable.ActionBar_displayOptions, 0));
129
130            final int customNavId = a.getResourceId(
131                    R.styleable.ActionBar_customNavigationLayout, 0);
132            if (customNavId != 0) {
133                setCustomView(LayoutInflater.from(mToolbar.getContext()).inflate(customNavId,
134                        mToolbar, false));
135                setDisplayOptions(mDisplayOpts | ActionBar.DISPLAY_SHOW_CUSTOM);
136            }
137
138            final int height = a.getLayoutDimension(R.styleable.ActionBar_height, 0);
139            if (height > 0) {
140                final ViewGroup.LayoutParams lp = mToolbar.getLayoutParams();
141                lp.height = height;
142                mToolbar.setLayoutParams(lp);
143            }
144
145            final int contentInsetStart = a.getDimensionPixelOffset(
146                    R.styleable.ActionBar_contentInsetStart, -1);
147            final int contentInsetEnd = a.getDimensionPixelOffset(
148                    R.styleable.ActionBar_contentInsetEnd, -1);
149            if (contentInsetStart >= 0 || contentInsetEnd >= 0) {
150                mToolbar.setContentInsetsRelative(Math.max(contentInsetStart, 0),
151                        Math.max(contentInsetEnd, 0));
152            }
153
154            final int titleTextStyle = a.getResourceId(R.styleable.ActionBar_titleTextStyle, 0);
155            if (titleTextStyle != 0) {
156                mToolbar.setTitleTextAppearance(mToolbar.getContext(), titleTextStyle);
157            }
158
159            final int subtitleTextStyle = a.getResourceId(
160                    R.styleable.ActionBar_subtitleTextStyle, 0);
161            if (subtitleTextStyle != 0) {
162                mToolbar.setSubtitleTextAppearance(mToolbar.getContext(), subtitleTextStyle);
163            }
164
165            final int popupTheme = a.getResourceId(R.styleable.ActionBar_popupTheme, 0);
166            if (popupTheme != 0) {
167                mToolbar.setPopupTheme(popupTheme);
168            }
169
170            a.recycle();
171            // Keep the TintManager in case we need it later
172            mTintManager = a.getTintManager();
173        } else {
174            mDisplayOpts = detectDisplayOptions();
175            // Create a TintManager in case we need it later
176            mTintManager = new TintManager(toolbar.getContext());
177        }
178
179        setDefaultNavigationContentDescription(defaultNavigationContentDescription);
180        mHomeDescription = mToolbar.getNavigationContentDescription();
181
182        setDefaultNavigationIcon(mTintManager.getDrawable(defaultNavigationIcon));
183
184        mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
185            final ActionMenuItem mNavItem = new ActionMenuItem(mToolbar.getContext(),
186                    0, android.R.id.home, 0, 0, mTitle);
187            @Override
188            public void onClick(View v) {
189                if (mWindowCallback != null && mMenuPrepared) {
190                    mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mNavItem);
191                }
192            }
193        });
194    }
195
196    /**
197     * Sets the default content description for the navigation button.
198     * <p>
199     * It changes the current content description if and only if the provided resource id is
200     * different than the current default resource id and the current content description is empty.
201     *
202     * @param defaultNavigationContentDescription The resource id for the default content
203     *                                            description
204     */
205    @Override
206    public void setDefaultNavigationContentDescription(int defaultNavigationContentDescription) {
207        if (defaultNavigationContentDescription == mDefaultNavigationContentDescription) {
208            return;
209        }
210        mDefaultNavigationContentDescription = defaultNavigationContentDescription;
211        if (TextUtils.isEmpty(mToolbar.getNavigationContentDescription())) {
212            setNavigationContentDescription(mDefaultNavigationContentDescription);
213        }
214    }
215
216    @Override
217    public void setDefaultNavigationIcon(Drawable defaultNavigationIcon) {
218        if (mDefaultNavigationIcon != defaultNavigationIcon) {
219            mDefaultNavigationIcon = defaultNavigationIcon;
220            updateNavigationIcon();
221        }
222    }
223
224    private int detectDisplayOptions() {
225        int opts = ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_HOME |
226                ActionBar.DISPLAY_USE_LOGO;
227        if (mToolbar.getNavigationIcon() != null) {
228            opts |= ActionBar.DISPLAY_HOME_AS_UP;
229        }
230        return opts;
231    }
232
233    @Override
234    public ViewGroup getViewGroup() {
235        return mToolbar;
236    }
237
238    @Override
239    public Context getContext() {
240        return mToolbar.getContext();
241    }
242
243    @Override
244    public boolean isSplit() {
245        return false;
246    }
247
248    @Override
249    public boolean hasExpandedActionView() {
250        return mToolbar.hasExpandedActionView();
251    }
252
253    @Override
254    public void collapseActionView() {
255        mToolbar.collapseActionView();
256    }
257
258    @Override
259    public void setWindowCallback(WindowCallback cb) {
260        mWindowCallback = cb;
261    }
262
263    @Override
264    public void setWindowTitle(CharSequence title) {
265        // "Real" title always trumps window title.
266        if (!mTitleSet) {
267            setTitleInt(title);
268        }
269    }
270
271    @Override
272    public CharSequence getTitle() {
273        return mToolbar.getTitle();
274    }
275
276    @Override
277    public void setTitle(CharSequence title) {
278        mTitleSet = true;
279        setTitleInt(title);
280    }
281
282    private void setTitleInt(CharSequence title) {
283        mTitle = title;
284        if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
285            mToolbar.setTitle(title);
286        }
287    }
288
289    @Override
290    public CharSequence getSubtitle() {
291        return mToolbar.getSubtitle();
292    }
293
294    @Override
295    public void setSubtitle(CharSequence subtitle) {
296        mSubtitle = subtitle;
297        if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
298            mToolbar.setSubtitle(subtitle);
299        }
300    }
301
302    @Override
303    public void initProgress() {
304        Log.i(TAG, "Progress display unsupported");
305    }
306
307    @Override
308    public void initIndeterminateProgress() {
309        Log.i(TAG, "Progress display unsupported");
310    }
311
312    @Override
313    public boolean canSplit() {
314        return false;
315    }
316
317    @Override
318    public void setSplitView(ViewGroup splitView) {
319    }
320
321    @Override
322    public void setSplitToolbar(boolean split) {
323        if (split) {
324            throw new UnsupportedOperationException("Cannot split an android.widget.Toolbar");
325        }
326    }
327
328    @Override
329    public void setSplitWhenNarrow(boolean splitWhenNarrow) {
330        // Ignore.
331    }
332
333    @Override
334    public boolean hasIcon() {
335        return mIcon != null;
336    }
337
338    @Override
339    public boolean hasLogo() {
340        return mLogo != null;
341    }
342
343    @Override
344    public void setIcon(int resId) {
345        setIcon(resId != 0 ? mTintManager.getDrawable(resId) : null);
346    }
347
348    @Override
349    public void setIcon(Drawable d) {
350        mIcon = d;
351        updateToolbarLogo();
352    }
353
354    @Override
355    public void setLogo(int resId) {
356        setLogo(resId != 0 ? mTintManager.getDrawable(resId) : null);
357    }
358
359    @Override
360    public void setLogo(Drawable d) {
361        mLogo = d;
362        updateToolbarLogo();
363    }
364
365    private void updateToolbarLogo() {
366        Drawable logo = null;
367        if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_HOME) != 0) {
368            if ((mDisplayOpts & ActionBar.DISPLAY_USE_LOGO) != 0) {
369                logo = mLogo != null ? mLogo : mIcon;
370            } else {
371                logo = mIcon;
372            }
373        }
374        mToolbar.setLogo(logo);
375    }
376
377    @Override
378    public boolean canShowOverflowMenu() {
379        return mToolbar.canShowOverflowMenu();
380    }
381
382    @Override
383    public boolean isOverflowMenuShowing() {
384        return mToolbar.isOverflowMenuShowing();
385    }
386
387    @Override
388    public boolean isOverflowMenuShowPending() {
389        return mToolbar.isOverflowMenuShowPending();
390    }
391
392    @Override
393    public boolean showOverflowMenu() {
394        return mToolbar.showOverflowMenu();
395    }
396
397    @Override
398    public boolean hideOverflowMenu() {
399        return mToolbar.hideOverflowMenu();
400    }
401
402    @Override
403    public void setMenuPrepared() {
404        mMenuPrepared = true;
405    }
406
407    @Override
408    public void setMenu(Menu menu, MenuPresenter.Callback cb) {
409        if (mActionMenuPresenter == null) {
410            mActionMenuPresenter = new ActionMenuPresenter(mToolbar.getContext());
411            mActionMenuPresenter.setId(R.id.action_menu_presenter);
412        }
413        mActionMenuPresenter.setCallback(cb);
414        mToolbar.setMenu((MenuBuilder) menu, mActionMenuPresenter);
415    }
416
417    @Override
418    public void dismissPopupMenus() {
419        mToolbar.dismissPopupMenus();
420    }
421
422    @Override
423    public int getDisplayOptions() {
424        return mDisplayOpts;
425    }
426
427    @Override
428    public void setDisplayOptions(int newOpts) {
429        final int oldOpts = mDisplayOpts;
430        final int changed = oldOpts ^ newOpts;
431        mDisplayOpts = newOpts;
432        if (changed != 0) {
433            if ((changed & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
434                if ((newOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
435                    updateNavigationIcon();
436                    updateHomeAccessibility();
437                } else {
438                    mToolbar.setNavigationIcon(null);
439                }
440            }
441
442            if ((changed & AFFECTS_LOGO_MASK) != 0) {
443                updateToolbarLogo();
444            }
445
446            if ((changed & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
447                if ((newOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
448                    mToolbar.setTitle(mTitle);
449                    mToolbar.setSubtitle(mSubtitle);
450                } else {
451                    mToolbar.setTitle(null);
452                    mToolbar.setSubtitle(null);
453                }
454            }
455
456            if ((changed & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomView != null) {
457                if ((newOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
458                    mToolbar.addView(mCustomView);
459                } else {
460                    mToolbar.removeView(mCustomView);
461                }
462            }
463        }
464    }
465
466    @Override
467    public void setEmbeddedTabView(ScrollingTabContainerView tabView) {
468        if (mTabView != null && mTabView.getParent() == mToolbar) {
469            mToolbar.removeView(mTabView);
470        }
471        mTabView = tabView;
472        if (tabView != null && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) {
473            mToolbar.addView(mTabView, 0);
474            Toolbar.LayoutParams lp = (Toolbar.LayoutParams) mTabView.getLayoutParams();
475            lp.width = ViewGroup.LayoutParams.WRAP_CONTENT;
476            lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
477            lp.gravity = Gravity.START | Gravity.BOTTOM;
478            tabView.setAllowCollapse(true);
479        }
480    }
481
482    @Override
483    public boolean hasEmbeddedTabs() {
484        return mTabView != null;
485    }
486
487    @Override
488    public boolean isTitleTruncated() {
489        return mToolbar.isTitleTruncated();
490    }
491
492    @Override
493    public void setCollapsible(boolean collapsible) {
494        mToolbar.setCollapsible(collapsible);
495    }
496
497    @Override
498    public void setHomeButtonEnabled(boolean enable) {
499        // Ignore
500    }
501
502    @Override
503    public int getNavigationMode() {
504        return mNavigationMode;
505    }
506
507    @Override
508    public void setNavigationMode(int mode) {
509        final int oldMode = mNavigationMode;
510        if (mode != oldMode) {
511            switch (oldMode) {
512                case ActionBar.NAVIGATION_MODE_LIST:
513                    if (mSpinner != null && mSpinner.getParent() == mToolbar) {
514                        mToolbar.removeView(mSpinner);
515                    }
516                    break;
517                case ActionBar.NAVIGATION_MODE_TABS:
518                    if (mTabView != null && mTabView.getParent() == mToolbar) {
519                        mToolbar.removeView(mTabView);
520                    }
521                    break;
522            }
523
524            mNavigationMode = mode;
525
526            switch (mode) {
527                case ActionBar.NAVIGATION_MODE_STANDARD:
528                    break;
529                case ActionBar.NAVIGATION_MODE_LIST:
530                    ensureSpinner();
531                    mToolbar.addView(mSpinner, 0);
532                    break;
533                case ActionBar.NAVIGATION_MODE_TABS:
534                    if (mTabView != null) {
535                        mToolbar.addView(mTabView, 0);
536                        Toolbar.LayoutParams lp = (Toolbar.LayoutParams) mTabView.getLayoutParams();
537                        lp.width = ViewGroup.LayoutParams.WRAP_CONTENT;
538                        lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
539                        lp.gravity = Gravity.START | Gravity.BOTTOM;
540                    }
541                    break;
542                default:
543                    throw new IllegalArgumentException("Invalid navigation mode " + mode);
544            }
545        }
546    }
547
548    private void ensureSpinner() {
549        if (mSpinner == null) {
550            mSpinner = new SpinnerCompat(getContext(), null, R.attr.actionDropDownStyle);
551            Toolbar.LayoutParams lp = new Toolbar.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
552                    ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL);
553            mSpinner.setLayoutParams(lp);
554        }
555    }
556
557    @Override
558    public void setDropdownParams(SpinnerAdapter adapter,
559            AdapterViewCompat.OnItemSelectedListener listener) {
560        ensureSpinner();
561        mSpinner.setAdapter(adapter);
562        mSpinner.setOnItemSelectedListener(listener);
563    }
564
565    @Override
566    public void setDropdownSelectedPosition(int position) {
567        if (mSpinner == null) {
568            throw new IllegalStateException(
569                    "Can't set dropdown selected position without an adapter");
570        }
571        mSpinner.setSelection(position);
572    }
573
574    @Override
575    public int getDropdownSelectedPosition() {
576        return mSpinner != null ? mSpinner.getSelectedItemPosition() : 0;
577    }
578
579    @Override
580    public int getDropdownItemCount() {
581        return mSpinner != null ? mSpinner.getCount() : 0;
582    }
583
584    @Override
585    public void setCustomView(View view) {
586        if (mCustomView != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
587            mToolbar.removeView(mCustomView);
588        }
589        mCustomView = view;
590        if (view != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
591            mToolbar.addView(mCustomView);
592        }
593    }
594
595    @Override
596    public View getCustomView() {
597        return mCustomView;
598    }
599
600    @Override
601    public void animateToVisibility(int visibility) {
602        if (visibility == View.GONE) {
603            ViewCompat.animate(mToolbar).alpha(0)
604                    .setListener(new ViewPropertyAnimatorListenerAdapter() {
605                        private boolean mCanceled = false;
606                        @Override
607                        public void onAnimationEnd(View view) {
608                            if (!mCanceled) {
609                                mToolbar.setVisibility(View.GONE);
610                            }
611                        }
612
613                        @Override
614                        public void onAnimationCancel(View view) {
615                            mCanceled = true;
616                        }
617                    });
618        } else if (visibility == View.VISIBLE) {
619            ViewCompat.animate(mToolbar).alpha(1)
620                    .setListener(new ViewPropertyAnimatorListenerAdapter() {
621                        @Override
622                        public void onAnimationStart(View view) {
623                            mToolbar.setVisibility(View.VISIBLE);
624                        }
625                    });
626        }
627    }
628
629    @Override
630    public void setNavigationIcon(Drawable icon) {
631        mNavIcon = icon;
632        updateNavigationIcon();
633    }
634
635    @Override
636    public void setNavigationIcon(int resId) {
637        setNavigationIcon(resId != 0
638                ? mTintManager.getDrawable(resId)
639                : null);
640    }
641
642    @Override
643    public void setNavigationContentDescription(CharSequence description) {
644        mHomeDescription = description;
645        updateHomeAccessibility();
646    }
647
648    @Override
649    public void setNavigationContentDescription(int resId) {
650        setNavigationContentDescription(resId == 0 ? null : getContext().getString(resId));
651    }
652
653    private void updateHomeAccessibility() {
654        if ((mDisplayOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
655            if (TextUtils.isEmpty(mHomeDescription)) {
656                mToolbar.setNavigationContentDescription(mDefaultNavigationContentDescription);
657            } else {
658                mToolbar.setNavigationContentDescription(mHomeDescription);
659            }
660        }
661    }
662
663    private void updateNavigationIcon() {
664        if ((mDisplayOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
665            mToolbar.setNavigationIcon(mNavIcon != null ? mNavIcon : mDefaultNavigationIcon);
666        }
667    }
668
669    @Override
670    public void saveHierarchyState(SparseArray<Parcelable> toolbarStates) {
671        mToolbar.saveHierarchyState(toolbarStates);
672    }
673
674    @Override
675    public void restoreHierarchyState(SparseArray<Parcelable> toolbarStates) {
676        mToolbar.restoreHierarchyState(toolbarStates);
677    }
678
679}