[go: nahoru, domu]

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