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