BottomNavigationView.java revision f6a12ad68e652ba7cb2d21b9285f33a770a5e537
1/*
2 * Copyright (C) 2016 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.os.Parcelable;
22import android.support.annotation.DrawableRes;
23import android.support.annotation.NonNull;
24import android.support.annotation.Nullable;
25import android.support.design.R;
26import android.support.design.internal.BottomNavigationMenu;
27import android.support.design.internal.BottomNavigationMenuView;
28import android.support.design.internal.BottomNavigationPresenter;
29import android.support.v7.content.res.AppCompatResources;
30import android.support.v7.view.SupportMenuInflater;
31import android.support.v7.view.menu.MenuBuilder;
32import android.support.v7.widget.TintTypedArray;
33import android.util.AttributeSet;
34import android.util.TypedValue;
35import android.view.Menu;
36import android.view.MenuInflater;
37import android.view.MenuItem;
38import android.view.ViewGroup;
39import android.widget.FrameLayout;
40import android.widget.LinearLayout;
41
42/**
43 * <p>
44 * Represents a standard bottom navigation bar for application. It is an implementation of
45 * <a href="https://material.google.com/components/bottom-navigation.html">material design bottom
46 * navigation</a>.
47 * </p>
48 *
49 * <p>
50 * Bottom navigation bars make it easy for users to explore and switch between top-level views in
51 * a single tap. It should be used when application has three to five top-level destinations.
52 * </p>
53 *
54 * <p>
55 * The bar contents can be populated by specifying a menu resource file. Each menu item title, icon
56 * and enabled state will be used for displaying bottom navigation bar items.
57 * </p>
58 *
59 * <pre>
60 * &lt;android.support.design.widget.BottomNavigationView
61 *     xmlns:android="http://schemas.android.com/apk/res/android"
62 *     xmlns:app="http://schemas.android.com/apk/res-auto"
63 *     android:id="@+id/navigation"
64 *     android:layout_width="wrap_content"
65 *     android:layout_height="match_parent"
66 *     android:layout_gravity="start"
67 *     app:menu="@menu/my_navigation_items" /&gt;
68 * </pre>
69 */
70public class BottomNavigationView extends FrameLayout {
71
72    private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};
73    private static final int[] DISABLED_STATE_SET = {-android.R.attr.state_enabled};
74
75    private final MenuBuilder mMenu;
76    private final BottomNavigationMenuView mMenuView;
77    private final BottomNavigationPresenter mPresenter = new BottomNavigationPresenter();
78    private MenuInflater mMenuInflater;
79
80    private OnNavigationItemSelectedListener mListener;
81
82    public BottomNavigationView(Context context) {
83        this(context, null);
84    }
85
86    public BottomNavigationView(Context context, AttributeSet attrs) {
87        this(context, attrs, 0);
88    }
89
90    public BottomNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
91        super(context, attrs, defStyleAttr);
92
93        ThemeUtils.checkAppCompatTheme(context);
94
95        // Create the menu
96        mMenu = new BottomNavigationMenu(context);
97
98        mMenuView = new BottomNavigationMenuView(context);
99        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
100                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
101        mMenuView.setLayoutParams(params);
102
103        mPresenter.setBottomNavigationMenuView(mMenuView);
104        mMenuView.setPresenter(mPresenter);
105        mMenu.addMenuPresenter(mPresenter);
106
107
108        // Custom attributes
109        TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
110                R.styleable.BottomNavigationView, defStyleAttr,
111                R.style.Widget_Design_BottomNavigationView);
112
113        if (a.hasValue(R.styleable.BottomNavigationView_itemIconTint)) {
114            mMenuView.setIconTintList(
115                    a.getColorStateList(R.styleable.BottomNavigationView_itemIconTint));
116        } else {
117            mMenuView.setIconTintList(
118                    createDefaultColorStateList(android.R.attr.textColorSecondary));
119        }
120        if (a.hasValue(R.styleable.BottomNavigationView_itemTextColor)) {
121            mMenuView.setItemTextColor(
122                    a.getColorStateList(R.styleable.BottomNavigationView_itemTextColor));
123        } else {
124            mMenuView.setItemTextColor(
125                    createDefaultColorStateList(android.R.attr.textColorSecondary));
126        }
127
128        int itemBackground = a.getResourceId(R.styleable.BottomNavigationView_itemBackground, 0);
129        mMenuView.setItemBackgroundRes(itemBackground);
130
131        if (a.hasValue(R.styleable.BottomNavigationView_menu)) {
132            inflateMenu(a.getResourceId(R.styleable.BottomNavigationView_menu, 0));
133        }
134        a.recycle();
135
136        addView(mMenuView);
137
138        mMenu.setCallback(new MenuBuilder.Callback() {
139            @Override
140            public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
141                return mListener != null && mListener.onNavigationItemSelected(item);
142            }
143
144            @Override
145            public void onMenuModeChange(MenuBuilder menu) {}
146        });
147    }
148
149    /**
150     * Set a listener that will be notified when a bottom navigation item is selected.
151     *
152     * @param listener The listener to notify
153     */
154    public void setOnNavigationItemSelectedListener(
155            @Nullable OnNavigationItemSelectedListener listener) {
156        mListener = listener;
157    }
158
159    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
160        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
161        if (mMenuView.updateOnSizeChange(getMeasuredWidth())) {
162            // updateOnSizeChanged has changed LPs, so we need to remeasure
163            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
164        }
165    }
166
167    /**
168     * Returns the {@link Menu} instance associated with this bottom navigation bar.
169     */
170    @NonNull
171    public Menu getMenu() {
172        return mMenu;
173    }
174
175    /**
176     * Inflate a menu resource into this navigation view.
177     *
178     * <p>Existing items in the menu will not be modified or removed.</p>
179     *
180     * @param resId ID of a menu resource to inflate
181     */
182    public void inflateMenu(int resId) {
183        mPresenter.setUpdateSuspended(true);
184        getMenuInflater().inflate(resId, mMenu);
185        mPresenter.initForMenu(getContext(), mMenu);
186        mPresenter.setUpdateSuspended(false);
187        mPresenter.updateMenuView(true);
188    }
189
190    /**
191     * @return The maximum number of items that can be shown in BottomNavigationView.
192     */
193    public int getMaxItemCount() {
194        return BottomNavigationMenu.MAX_ITEM_COUNT;
195    }
196
197    /**
198     * Returns the tint which is applied to our menu items' icons.
199     *
200     * @see #setItemIconTintList(ColorStateList)
201     *
202     * @attr ref R.styleable#BottomNavigationView_itemIconTint
203     */
204    @Nullable
205    public ColorStateList getItemIconTintList() {
206        return mMenuView.getIconTintList();
207    }
208
209    /**
210     * Set the tint which is applied to our menu items' icons.
211     *
212     * @param tint the tint to apply.
213     *
214     * @attr ref R.styleable#BottomNavigationView_itemIconTint
215     */
216    public void setItemIconTintList(@Nullable ColorStateList tint) {
217        mMenuView.setIconTintList(tint);
218    }
219
220    /**
221     * Returns the tint which is applied to menu items' icons.
222     *
223     * @see #setItemTextColor(ColorStateList)
224     *
225     * @attr ref R.styleable#BottomNavigationView_itemTextColor
226     */
227    @Nullable
228    public ColorStateList getItemTextColor() {
229        return mMenuView.getItemTextColor();
230    }
231
232    /**
233     * Set the text color to be used on menu items.
234     *
235     * @see #getItemTextColor()
236     *
237     * @attr ref R.styleable#BottomNavigationView_itemTextColor
238     */
239    public void setItemTextColor(@Nullable ColorStateList textColor) {
240        mMenuView.setItemTextColor(textColor);
241    }
242
243    /**
244     * Returns the background resource of the menu items.
245     *
246     * @see #setItemBackgroundResource(int)
247     *
248     * @attr ref R.styleable#BottomNavigationView_itemBackground
249     */
250    @DrawableRes
251    public int getItemBackgroundResource() {
252        return mMenuView.getItemBackgroundRes();
253    }
254
255    /**
256     * Set the background of our menu items to the given resource.
257     *
258     * @param resId The identifier of the resource.
259     *
260     * @attr ref R.styleable#BottomNavigationView_itemBackground
261     */
262    public void setItemBackgroundResource(@DrawableRes int resId) {
263        mMenuView.setItemBackgroundRes(resId);
264    }
265
266    /**
267     * Listener for handling events on bottom navigation items.
268     */
269    public interface OnNavigationItemSelectedListener {
270
271        /**
272         * Called when an item in the bottom navigation menu is selected.
273         *
274         * @param item The selected item
275         *
276         * @return true to display the item as the selected item
277         */
278        public boolean onNavigationItemSelected(@NonNull MenuItem item);
279    }
280
281    private MenuInflater getMenuInflater() {
282        if (mMenuInflater == null) {
283            mMenuInflater = new SupportMenuInflater(getContext());
284        }
285        return mMenuInflater;
286    }
287
288    private ColorStateList createDefaultColorStateList(int baseColorThemeAttr) {
289        final TypedValue value = new TypedValue();
290        if (!getContext().getTheme().resolveAttribute(baseColorThemeAttr, value, true)) {
291            return null;
292        }
293        ColorStateList baseColor = AppCompatResources.getColorStateList(
294                getContext(), value.resourceId);
295        if (!getContext().getTheme().resolveAttribute(
296                android.support.v7.appcompat.R.attr.colorPrimary, value, true)) {
297            return null;
298        }
299        int colorPrimary = value.data;
300        int defaultColor = baseColor.getDefaultColor();
301        return new ColorStateList(new int[][]{
302                DISABLED_STATE_SET,
303                CHECKED_STATE_SET,
304                EMPTY_STATE_SET
305        }, new int[]{
306                baseColor.getColorForState(DISABLED_STATE_SET, defaultColor),
307                colorPrimary,
308                defaultColor
309        });
310    }
311}
312