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