BottomNavigationMenuView.java revision 3cc432a5bd723a79dc52438235b47d0ea5d41ae4
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.internal;
18
19import android.content.Context;
20import android.content.res.ColorStateList;
21import android.content.res.Resources;
22import android.os.Build;
23import android.support.annotation.Nullable;
24import android.support.design.R;
25import android.support.v4.util.Pools;
26import android.support.v4.view.ViewCompat;
27import android.support.v7.view.menu.MenuBuilder;
28import android.support.v7.view.menu.MenuItemImpl;
29import android.support.v7.view.menu.MenuView;
30import android.util.AttributeSet;
31import android.view.View;
32import android.view.ViewGroup;
33
34/**
35 * @hide
36 */
37public class BottomNavigationMenuView extends ViewGroup implements MenuView {
38
39    private final int mInactiveItemMaxWidth;
40    private final int mInactiveItemMinWidth;
41    private final int mActiveItemMaxWidth;
42    private final int mItemHeight;
43    private final OnClickListener mOnClickListener;
44    private final BottomNavigationAnimationHelperBase mAnimationHelper;
45    private static final Pools.Pool<BottomNavigationItemView> sItemPool =
46            new Pools.SynchronizedPool<>(5);
47
48    private boolean mShiftingMode = true;
49
50    private BottomNavigationItemView[] mButtons;
51    private int mActiveButton = 0;
52    private ColorStateList mItemIconTint;
53    private ColorStateList mItemTextColor;
54    private int mItemBackgroundRes;
55
56    private BottomNavigationPresenter mPresenter;
57    private MenuBuilder mMenu;
58
59    public BottomNavigationMenuView(Context context) {
60        this(context, null);
61    }
62
63    public BottomNavigationMenuView(Context context, AttributeSet attrs) {
64        super(context, attrs);
65        final Resources res = getResources();
66        mInactiveItemMaxWidth = res.getDimensionPixelSize(
67                R.dimen.design_bottom_navigation_item_max_width);
68        mInactiveItemMinWidth = res.getDimensionPixelSize(
69                R.dimen.design_bottom_navigation_item_min_width);
70        mActiveItemMaxWidth = res.getDimensionPixelSize(
71                R.dimen.design_bottom_navigation_active_item_max_width);
72        mItemHeight = res.getDimensionPixelSize(R.dimen.design_bottom_navigation_height);
73
74        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
75            mAnimationHelper = new BottomNavigationAnimationHelperIcs();
76        } else {
77            mAnimationHelper = new BottomNavigationAnimationHelperBase();
78        }
79
80        mOnClickListener = new OnClickListener() {
81            @Override
82            public void onClick(View v) {
83                final BottomNavigationItemView itemView = (BottomNavigationItemView) v;
84                final int itemPosition = itemView.getItemPosition();
85                activateNewButton(itemPosition);
86                mMenu.performItemAction(itemView.getItemData(), mPresenter, 0);
87            }
88        };
89    }
90
91    @Override
92    public void initialize(MenuBuilder menu) {
93        mMenu = menu;
94        if (mMenu == null) return;
95        if (mMenu.size() > mActiveButton) {
96            mMenu.getItem(mActiveButton).setChecked(true);
97        }
98    }
99
100    @Override
101    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
102        final int width = MeasureSpec.getSize(widthMeasureSpec);
103        final int count = getChildCount();
104
105        final int childState = 0;
106        final int heightSpec = MeasureSpec.makeMeasureSpec(mItemHeight, MeasureSpec.EXACTLY);
107
108        final int[] childWidths = new int[count];
109        if (mShiftingMode) {
110            final int inactiveCount = count - 1;
111            final int activeMaxAvailable = width - inactiveCount * mInactiveItemMinWidth;
112            final int activeWidth = Math.min(activeMaxAvailable, mActiveItemMaxWidth);
113            final int inactiveMaxAvailable = (width - activeWidth) / inactiveCount;
114            final int inactiveWidth = Math.min(inactiveMaxAvailable, mInactiveItemMaxWidth);
115            int extra = width - activeWidth - inactiveWidth * inactiveCount;
116            for (int i = 0; i < count; i++) {
117                childWidths[i] = (i == mActiveButton) ? activeWidth : inactiveWidth;
118                if (extra > 0) {
119                    childWidths[i]++;
120                    extra--;
121                }
122            }
123        } else {
124            final int maxAvailable = width / count;
125            final int childWidth = Math.min(maxAvailable, mActiveItemMaxWidth);
126            int extra = width - childWidth * count;
127            for (int i = 0; i < count; i++) {
128                childWidths[i] = childWidth;
129                if (extra > 0) {
130                    childWidths[i]++;
131                    extra--;
132                }
133            }
134        }
135
136        int totalWidth = 0;
137        for (int i = 0; i < count; i++) {
138            final View child = getChildAt(i);
139            if (child.getVisibility() == GONE) {
140                continue;
141            }
142            child.measure(MeasureSpec.makeMeasureSpec(childWidths[i], MeasureSpec.EXACTLY),
143                    heightSpec);
144            ViewGroup.LayoutParams params = child.getLayoutParams();
145            params.width = child.getMeasuredWidth();
146            totalWidth += child.getMeasuredWidth();
147        }
148        setMeasuredDimension(
149                ViewCompat.resolveSizeAndState(totalWidth,
150                        MeasureSpec.makeMeasureSpec(totalWidth, MeasureSpec.EXACTLY), childState),
151                ViewCompat.resolveSizeAndState(mItemHeight, heightSpec,
152                        childState << MEASURED_HEIGHT_STATE_SHIFT));
153    }
154
155    @Override
156    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
157        final int count = getChildCount();
158        final int width = right - left;
159        final int height = bottom - top;
160        int used = 0;
161        for (int i = 0; i < count; i++) {
162            final View child = getChildAt(i);
163            if (child.getVisibility() == GONE) {
164                continue;
165            }
166            if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL) {
167                child.layout(width - used - child.getMeasuredWidth(), 0, width - used, height);
168            } else {
169                child.layout(used, 0, child.getMeasuredWidth() + used, height);
170            }
171            used += child.getMeasuredWidth();
172        }
173    }
174
175    @Override
176    public int getWindowAnimations() {
177        return 0;
178    }
179
180    public void setIconTintList(ColorStateList color) {
181        mItemIconTint = color;
182        if (mButtons == null) return;
183        for (BottomNavigationItemView item : mButtons) {
184            item.setIconTintList(color);
185        }
186    }
187
188    @Nullable
189    public ColorStateList getIconTintList() {
190        return mItemIconTint;
191    }
192
193    public void setItemTextColor(ColorStateList color) {
194        mItemTextColor = color;
195        if (mButtons == null) return;
196        for (BottomNavigationItemView item : mButtons) {
197            item.setTextColor(color);
198        }
199    }
200
201    public ColorStateList getItemTextColor() {
202        return mItemTextColor;
203    }
204
205    public void setItemBackgroundRes(int background) {
206        mItemBackgroundRes = background;
207        if (mButtons == null) return;
208        for (BottomNavigationItemView item : mButtons) {
209            item.setItemBackground(background);
210        }
211    }
212
213    public int getItemBackgroundRes() {
214        return mItemBackgroundRes;
215    }
216
217    public void setPresenter(BottomNavigationPresenter presenter) {
218        mPresenter = presenter;
219    }
220
221    public void buildMenuView() {
222        if (mButtons != null) {
223            for (BottomNavigationItemView item : mButtons) {
224                sItemPool.release(item);
225            }
226        }
227        removeAllViews();
228        mButtons = new BottomNavigationItemView[mMenu.size()];
229        mShiftingMode = mMenu.size() > 3;
230        for (int i = 0; i < mMenu.size(); i++) {
231            mPresenter.setUpdateSuspended(true);
232            mMenu.getItem(i).setCheckable(true);
233            mPresenter.setUpdateSuspended(false);
234            BottomNavigationItemView child = getNewItem();
235            mButtons[i] = child;
236            child.setIconTintList(mItemIconTint);
237            child.setTextColor(mItemTextColor);
238            child.setItemBackground(mItemBackgroundRes);
239            child.setShiftingMode(mShiftingMode);
240            child.initialize((MenuItemImpl) mMenu.getItem(i), 0);
241            child.setItemPosition(i);
242            child.setOnClickListener(mOnClickListener);
243            addView(child);
244        }
245    }
246
247    public void updateMenuView() {
248        final int menuSize = mMenu.size();
249        if (menuSize != mButtons.length) {
250            // The size has changed. Rebuild menu view from scratch.
251            buildMenuView();
252            return;
253        }
254        for (int i = 0; i < menuSize; i++) {
255            mPresenter.setUpdateSuspended(true);
256            mButtons[i].initialize((MenuItemImpl) mMenu.getItem(i), 0);
257            mPresenter.setUpdateSuspended(false);
258        }
259    }
260
261    private void activateNewButton(int newButton) {
262        if (mActiveButton == newButton) return;
263
264        mAnimationHelper.beginDelayedTransition(this);
265
266        mPresenter.setUpdateSuspended(true);
267        mButtons[mActiveButton].setChecked(false);
268        mButtons[newButton].setChecked(true);
269        mPresenter.setUpdateSuspended(false);
270
271        mActiveButton = newButton;
272    }
273
274    private BottomNavigationItemView getNewItem() {
275        BottomNavigationItemView item = sItemPool.acquire();
276        if (item == null) {
277            item = new BottomNavigationItemView(getContext());
278        }
279        return item;
280    }
281}
282