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