NavigationView.java revision 7e268c7de8bb10fc24ddda5d237d5cc5ce3b591b
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.design.widget;
18
19import android.content.Context;
20import android.content.res.ColorStateList;
21import android.content.res.TypedArray;
22import android.graphics.Rect;
23import android.graphics.drawable.Drawable;
24import android.os.Bundle;
25import android.os.Parcel;
26import android.os.Parcelable;
27import android.support.annotation.DrawableRes;
28import android.support.annotation.IdRes;
29import android.support.annotation.LayoutRes;
30import android.support.annotation.NonNull;
31import android.support.annotation.Nullable;
32import android.support.annotation.StyleRes;
33import android.support.design.R;
34import android.support.design.internal.NavigationMenu;
35import android.support.design.internal.NavigationMenuPresenter;
36import android.support.design.internal.ScrimInsetsFrameLayout;
37import android.support.v4.content.ContextCompat;
38import android.support.v4.os.ParcelableCompat;
39import android.support.v4.os.ParcelableCompatCreatorCallbacks;
40import android.support.v4.view.ViewCompat;
41import android.support.v7.view.SupportMenuInflater;
42import android.support.v7.view.menu.MenuBuilder;
43import android.support.v7.view.menu.MenuItemImpl;
44import android.util.AttributeSet;
45import android.util.TypedValue;
46import android.view.Menu;
47import android.view.MenuInflater;
48import android.view.MenuItem;
49import android.view.View;
50
51/**
52 * Represents a standard navigation menu for application. The menu contents can be populated
53 * by a menu resource file.
54 * <p>NavigationView is typically placed inside a {@link android.support.v4.widget.DrawerLayout}.
55 * </p>
56 * <pre>
57 * &lt;android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
58 *     xmlns:app="http://schemas.android.com/apk/res-auto"
59 *     android:id="@+id/drawer_layout"
60 *     android:layout_width="match_parent"
61 *     android:layout_height="match_parent"
62 *     android:fitsSystemWindows="true"&gt;
63 *
64 *     &lt;!-- Your contents --&gt;
65 *
66 *     &lt;android.support.design.widget.NavigationView
67 *         android:id="@+id/navigation"
68 *         android:layout_width="wrap_content"
69 *         android:layout_height="match_parent"
70 *         android:layout_gravity="start"
71 *         app:menu="@menu/my_navigation_items" /&gt;
72 * &lt;/android.support.v4.widget.DrawerLayout&gt;
73 * </pre>
74 */
75public class NavigationView extends ScrimInsetsFrameLayout {
76
77    private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};
78    private static final int[] DISABLED_STATE_SET = {-android.R.attr.state_enabled};
79
80    private static final int PRESENTER_NAVIGATION_VIEW_ID = 1;
81
82    private final NavigationMenu mMenu;
83    private final NavigationMenuPresenter mPresenter = new NavigationMenuPresenter();
84
85    private OnNavigationItemSelectedListener mListener;
86    private int mMaxWidth;
87
88    private MenuInflater mMenuInflater;
89
90    public NavigationView(Context context) {
91        this(context, null);
92    }
93
94    public NavigationView(Context context, AttributeSet attrs) {
95        this(context, attrs, R.attr.navigationViewStyle);
96    }
97
98    public NavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
99        super(context, attrs, defStyleAttr);
100
101        ThemeUtils.checkAppCompatTheme(context);
102
103        // Create the menu
104        mMenu = new NavigationMenu(context);
105
106        // Custom attributes
107        TypedArray a = context.obtainStyledAttributes(attrs,
108                R.styleable.NavigationView, defStyleAttr,
109                R.style.Widget_Design_NavigationView);
110
111        //noinspection deprecation
112        setBackgroundDrawable(a.getDrawable(R.styleable.NavigationView_android_background));
113        if (a.hasValue(R.styleable.NavigationView_elevation)) {
114            ViewCompat.setElevation(this, a.getDimensionPixelSize(
115                    R.styleable.NavigationView_elevation, 0));
116        }
117        ViewCompat.setFitsSystemWindows(this,
118                a.getBoolean(R.styleable.NavigationView_android_fitsSystemWindows, false));
119
120        mMaxWidth = a.getDimensionPixelSize(R.styleable.NavigationView_android_maxWidth, 0);
121
122        final ColorStateList itemIconTint;
123        if (a.hasValue(R.styleable.NavigationView_itemIconTint)) {
124            itemIconTint = a.getColorStateList(R.styleable.NavigationView_itemIconTint);
125        } else {
126            itemIconTint = createDefaultColorStateList(android.R.attr.textColorSecondary);
127        }
128
129        boolean textAppearanceSet = false;
130        int textAppearance = 0;
131        if (a.hasValue(R.styleable.NavigationView_itemTextAppearance)) {
132            textAppearance = a.getResourceId(R.styleable.NavigationView_itemTextAppearance, 0);
133            textAppearanceSet = true;
134        }
135
136        ColorStateList itemTextColor = null;
137        if (a.hasValue(R.styleable.NavigationView_itemTextColor)) {
138            itemTextColor = a.getColorStateList(R.styleable.NavigationView_itemTextColor);
139        }
140
141        if (!textAppearanceSet && itemTextColor == null) {
142            // If there isn't a text appearance set, we'll use a default text color
143            itemTextColor = createDefaultColorStateList(android.R.attr.textColorPrimary);
144        }
145
146        final Drawable itemBackground = a.getDrawable(R.styleable.NavigationView_itemBackground);
147
148        mMenu.setCallback(new MenuBuilder.Callback() {
149            @Override
150            public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
151                return mListener != null && mListener.onNavigationItemSelected(item);
152            }
153
154            @Override
155            public void onMenuModeChange(MenuBuilder menu) {}
156        });
157        mPresenter.setId(PRESENTER_NAVIGATION_VIEW_ID);
158        mPresenter.initForMenu(context, mMenu);
159        mPresenter.setItemIconTintList(itemIconTint);
160        if (textAppearanceSet) {
161            mPresenter.setItemTextAppearance(textAppearance);
162        }
163        mPresenter.setItemTextColor(itemTextColor);
164        mPresenter.setItemBackground(itemBackground);
165        mMenu.addMenuPresenter(mPresenter);
166        addView((View) mPresenter.getMenuView(this));
167
168        if (a.hasValue(R.styleable.NavigationView_menu)) {
169            inflateMenu(a.getResourceId(R.styleable.NavigationView_menu, 0));
170        }
171
172        if (a.hasValue(R.styleable.NavigationView_headerLayout)) {
173            inflateHeaderView(a.getResourceId(R.styleable.NavigationView_headerLayout, 0));
174        }
175
176        a.recycle();
177    }
178
179    @Override
180    protected Parcelable onSaveInstanceState() {
181        Parcelable superState = super.onSaveInstanceState();
182        SavedState state = new SavedState(superState);
183        state.menuState = new Bundle();
184        mMenu.savePresenterStates(state.menuState);
185        return state;
186    }
187
188    @Override
189    protected void onRestoreInstanceState(Parcelable savedState) {
190        SavedState state = (SavedState) savedState;
191        super.onRestoreInstanceState(state.getSuperState());
192        mMenu.restorePresenterStates(state.menuState);
193    }
194
195    /**
196     * Set a listener that will be notified when a menu item is clicked.
197     *
198     * @param listener The listener to notify
199     */
200    public void setNavigationItemSelectedListener(OnNavigationItemSelectedListener listener) {
201        mListener = listener;
202    }
203
204    @Override
205    protected void onMeasure(int widthSpec, int heightSpec) {
206        switch (MeasureSpec.getMode(widthSpec)) {
207            case MeasureSpec.EXACTLY:
208                // Nothing to do
209                break;
210            case MeasureSpec.AT_MOST:
211                widthSpec = MeasureSpec.makeMeasureSpec(
212                        Math.min(MeasureSpec.getSize(widthSpec), mMaxWidth), MeasureSpec.EXACTLY);
213                break;
214            case MeasureSpec.UNSPECIFIED:
215                widthSpec = MeasureSpec.makeMeasureSpec(mMaxWidth, MeasureSpec.EXACTLY);
216                break;
217        }
218        // Let super sort out the height
219        super.onMeasure(widthSpec, heightSpec);
220    }
221
222    /**
223     * @hide
224     */
225    @Override
226    protected void onInsetsChanged(Rect insets) {
227        mPresenter.setPaddingTopDefault(insets.top);
228    }
229
230    /**
231     * Inflate a menu resource into this navigation view.
232     *
233     * <p>Existing items in the menu will not be modified or removed.</p>
234     *
235     * @param resId ID of a menu resource to inflate
236     */
237    public void inflateMenu(int resId) {
238        mPresenter.setUpdateSuspended(true);
239        getMenuInflater().inflate(resId, mMenu);
240        mPresenter.setUpdateSuspended(false);
241        mPresenter.updateMenuView(false);
242    }
243
244    /**
245     * Returns the {@link Menu} instance associated with this navigation view.
246     */
247    public Menu getMenu() {
248        return mMenu;
249    }
250
251    /**
252     * Inflates a View and add it as a header of the navigation menu.
253     *
254     * @param res The layout resource ID.
255     * @return a newly inflated View.
256     */
257    public View inflateHeaderView(@LayoutRes int res) {
258        return mPresenter.inflateHeaderView(res);
259    }
260
261    /**
262     * Adds a View as a header of the navigation menu.
263     *
264     * @param view The view to be added as a header of the navigation menu.
265     */
266    public void addHeaderView(@NonNull View view) {
267        mPresenter.addHeaderView(view);
268    }
269
270    /**
271     * Removes a previously-added header view.
272     *
273     * @param view The view to remove
274     */
275    public void removeHeaderView(@NonNull View view) {
276        mPresenter.removeHeaderView(view);
277    }
278
279    /**
280     * Gets the number of headers in this NavigationView.
281     *
282     * @return A positive integer representing the number of headers.
283     */
284    public int getHeaderCount() {
285        return mPresenter.getHeaderCount();
286    }
287
288    /**
289     * Gets the header view at the specified position.
290     *
291     * @param index The position at which to get the view from.
292     * @return The header view the specified position or null if the position does not exist in this
293     * NavigationView.
294     */
295    public View getHeaderView(int index) {
296        return mPresenter.getHeaderView(index);
297    }
298
299    /**
300     * Returns the tint which is applied to our menu items' icons.
301     *
302     * @see #setItemIconTintList(ColorStateList)
303     *
304     * @attr ref R.styleable#NavigationView_itemIconTint
305     */
306    @Nullable
307    public ColorStateList getItemIconTintList() {
308        return mPresenter.getItemTintList();
309    }
310
311    /**
312     * Set the tint which is applied to our menu items' icons.
313     *
314     * @param tint the tint to apply.
315     *
316     * @attr ref R.styleable#NavigationView_itemIconTint
317     */
318    public void setItemIconTintList(@Nullable ColorStateList tint) {
319        mPresenter.setItemIconTintList(tint);
320    }
321
322    /**
323     * Returns the tint which is applied to our menu items' icons.
324     *
325     * @see #setItemTextColor(ColorStateList)
326     *
327     * @attr ref R.styleable#NavigationView_itemTextColor
328     */
329    @Nullable
330    public ColorStateList getItemTextColor() {
331        return mPresenter.getItemTextColor();
332    }
333
334    /**
335     * Set the text color to be used on our menu items.
336     *
337     * @see #getItemTextColor()
338     *
339     * @attr ref R.styleable#NavigationView_itemTextColor
340     */
341    public void setItemTextColor(@Nullable ColorStateList textColor) {
342        mPresenter.setItemTextColor(textColor);
343    }
344
345    /**
346     * Returns the background drawable for our menu items.
347     *
348     * @see #setItemBackgroundResource(int)
349     *
350     * @attr ref R.styleable#NavigationView_itemBackground
351     */
352    @Nullable
353    public Drawable getItemBackground() {
354        return mPresenter.getItemBackground();
355    }
356
357    /**
358     * Set the background of our menu items to the given resource.
359     *
360     * @param resId The identifier of the resource.
361     *
362     * @attr ref R.styleable#NavigationView_itemBackground
363     */
364    public void setItemBackgroundResource(@DrawableRes int resId) {
365        setItemBackground(ContextCompat.getDrawable(getContext(), resId));
366    }
367
368    /**
369     * Set the background of our menu items to a given resource. The resource should refer to
370     * a Drawable object or null to use the default background set on this navigation menu.
371     *
372     * @attr ref R.styleable#NavigationView_itemBackground
373     */
374    public void setItemBackground(@Nullable Drawable itemBackground) {
375        mPresenter.setItemBackground(itemBackground);
376    }
377
378    /**
379     * Sets the currently checked item in this navigation menu.
380     *
381     * @param id The item ID of the currently checked item.
382     */
383    public void setCheckedItem(@IdRes int id) {
384        MenuItem item = mMenu.findItem(id);
385        if (item != null) {
386            mPresenter.setCheckedItem((MenuItemImpl) item);
387        }
388    }
389
390    /**
391     * Set the text appearance of the menu items to a given resource.
392     *
393     * @attr ref R.styleable#NavigationView_itemTextAppearance
394     */
395    public void setItemTextAppearance(@StyleRes int resId) {
396        mPresenter.setItemTextAppearance(resId);
397    }
398
399    private MenuInflater getMenuInflater() {
400        if (mMenuInflater == null) {
401            mMenuInflater = new SupportMenuInflater(getContext());
402        }
403        return mMenuInflater;
404    }
405
406    private ColorStateList createDefaultColorStateList(int baseColorThemeAttr) {
407        TypedValue value = new TypedValue();
408        if (!getContext().getTheme().resolveAttribute(baseColorThemeAttr, value, true)) {
409            return null;
410        }
411        ColorStateList baseColor = getResources().getColorStateList(value.resourceId);
412        if (!getContext().getTheme().resolveAttribute(R.attr.colorControlActivated, value, true)) {
413            return null;
414        }
415        int colorControlActivated = value.data;
416        int defaultColor = baseColor.getDefaultColor();
417        return new ColorStateList(new int[][]{
418                DISABLED_STATE_SET,
419                CHECKED_STATE_SET,
420                EMPTY_STATE_SET
421        }, new int[]{
422                baseColor.getColorForState(DISABLED_STATE_SET, defaultColor),
423                colorControlActivated,
424                defaultColor
425        });
426    }
427
428    /**
429     * Listener for handling events on navigation items.
430     */
431    public interface OnNavigationItemSelectedListener {
432
433        /**
434         * Called when an item in the navigation menu is selected.
435         *
436         * @param item The selected item
437         *
438         * @return true to display the item as the selected item
439         */
440        public boolean onNavigationItemSelected(MenuItem item);
441    }
442
443    /**
444     * User interface state that is stored by NavigationView for implementing
445     * onSaveInstanceState().
446     */
447    public static class SavedState extends BaseSavedState {
448        public Bundle menuState;
449
450        public SavedState(Parcel in, ClassLoader loader) {
451            super(in);
452            menuState = in.readBundle(loader);
453        }
454
455        public SavedState(Parcelable superState) {
456            super(superState);
457        }
458
459        @Override
460        public void writeToParcel(@NonNull Parcel dest, int flags) {
461            super.writeToParcel(dest, flags);
462            dest.writeBundle(menuState);
463        }
464
465        public static final Parcelable.Creator<SavedState> CREATOR
466                = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
467            @Override
468            public SavedState createFromParcel(Parcel parcel, ClassLoader loader) {
469                return new SavedState(parcel, loader);
470            }
471
472            @Override
473            public SavedState[] newArray(int size) {
474                return new SavedState[size];
475            }
476        });
477    }
478
479}
480