BottomNavigationMenuView.java revision 69557a3a037e4124ecbc98560e84fa89472b3bf4
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 if (!mMenu.performItemAction(itemView.getItemData(), mPresenter, 0)) { 90 activateNewButton(itemPosition); 91 } 92 } 93 }; 94 mTempChildWidths = new int[BottomNavigationMenu.MAX_ITEM_COUNT]; 95 } 96 97 @Override 98 public void initialize(MenuBuilder menu) { 99 mMenu = menu; 100 } 101 102 @Override 103 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 104 final int width = MeasureSpec.getSize(widthMeasureSpec); 105 final int count = getChildCount(); 106 107 final int heightSpec = MeasureSpec.makeMeasureSpec(mItemHeight, MeasureSpec.EXACTLY); 108 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 mTempChildWidths[i] = (i == mActiveButton) ? activeWidth : inactiveWidth; 118 if (extra > 0) { 119 mTempChildWidths[i]++; 120 extra--; 121 } 122 } 123 } else { 124 final int maxAvailable = width / (count == 0 ? 1 : count); 125 final int childWidth = Math.min(maxAvailable, mActiveItemMaxWidth); 126 int extra = width - childWidth * count; 127 for (int i = 0; i < count; i++) { 128 mTempChildWidths[i] = childWidth; 129 if (extra > 0) { 130 mTempChildWidths[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(mTempChildWidths[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), 0), 151 ViewCompat.resolveSizeAndState(mItemHeight, heightSpec, 0)); 152 } 153 154 @Override 155 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 156 final int count = getChildCount(); 157 final int width = right - left; 158 final int height = bottom - top; 159 int used = 0; 160 for (int i = 0; i < count; i++) { 161 final View child = getChildAt(i); 162 if (child.getVisibility() == GONE) { 163 continue; 164 } 165 if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL) { 166 child.layout(width - used - child.getMeasuredWidth(), 0, width - used, height); 167 } else { 168 child.layout(used, 0, child.getMeasuredWidth() + used, height); 169 } 170 used += child.getMeasuredWidth(); 171 } 172 } 173 174 @Override 175 public int getWindowAnimations() { 176 return 0; 177 } 178 179 /** 180 * Sets the tint which is applied to the menu items' icons. 181 * 182 * @param tint the tint to apply 183 */ 184 public void setIconTintList(ColorStateList tint) { 185 mItemIconTint = tint; 186 if (mButtons == null) return; 187 for (BottomNavigationItemView item : mButtons) { 188 item.setIconTintList(tint); 189 } 190 } 191 192 /** 193 * Returns the tint which is applied to menu items' icons. 194 * 195 * @return the ColorStateList that is used to tint menu items' icons 196 */ 197 @Nullable 198 public ColorStateList getIconTintList() { 199 return mItemIconTint; 200 } 201 202 /** 203 * Sets the text color to be used on menu items. 204 * 205 * @param color the ColorStateList used for menu items' text. 206 */ 207 public void setItemTextColor(ColorStateList color) { 208 mItemTextColor = color; 209 if (mButtons == null) return; 210 for (BottomNavigationItemView item : mButtons) { 211 item.setTextColor(color); 212 } 213 } 214 215 /** 216 * Returns the text color used on menu items. 217 * 218 * @return the ColorStateList used for menu items' text 219 */ 220 public ColorStateList getItemTextColor() { 221 return mItemTextColor; 222 } 223 224 /** 225 * Sets the resource ID to be used for item background. 226 * 227 * @param background the resource ID of the background 228 */ 229 public void setItemBackgroundRes(int background) { 230 mItemBackgroundRes = background; 231 if (mButtons == null) return; 232 for (BottomNavigationItemView item : mButtons) { 233 item.setItemBackground(background); 234 } 235 } 236 237 /** 238 * Returns the resource ID for the background of the menu items. 239 * 240 * @return the resource ID for the background 241 */ 242 public int getItemBackgroundRes() { 243 return mItemBackgroundRes; 244 } 245 246 public void setPresenter(BottomNavigationPresenter presenter) { 247 mPresenter = presenter; 248 } 249 250 public void buildMenuView() { 251 if (mButtons != null) { 252 for (BottomNavigationItemView item : mButtons) { 253 sItemPool.release(item); 254 } 255 } 256 removeAllViews(); 257 if (mMenu.size() == 0) { 258 return; 259 } 260 mButtons = new BottomNavigationItemView[mMenu.size()]; 261 mShiftingMode = mMenu.size() > 3; 262 for (int i = 0; i < mMenu.size(); i++) { 263 mPresenter.setUpdateSuspended(true); 264 mMenu.getItem(i).setCheckable(true); 265 mPresenter.setUpdateSuspended(false); 266 BottomNavigationItemView child = getNewItem(); 267 mButtons[i] = child; 268 child.setIconTintList(mItemIconTint); 269 child.setTextColor(mItemTextColor); 270 child.setItemBackground(mItemBackgroundRes); 271 child.setShiftingMode(mShiftingMode); 272 child.initialize((MenuItemImpl) mMenu.getItem(i), 0); 273 child.setItemPosition(i); 274 child.setOnClickListener(mOnClickListener); 275 addView(child); 276 } 277 mActiveButton = Math.min(mMenu.size() - 1, mActiveButton); 278 mMenu.getItem(mActiveButton).setChecked(true); 279 } 280 281 public void updateMenuView() { 282 final int menuSize = mMenu.size(); 283 if (menuSize != mButtons.length) { 284 // The size has changed. Rebuild menu view from scratch. 285 buildMenuView(); 286 return; 287 } 288 for (int i = 0; i < menuSize; i++) { 289 mPresenter.setUpdateSuspended(true); 290 if (mMenu.getItem(i).isChecked()) { 291 mActiveButton = i; 292 } 293 mButtons[i].initialize((MenuItemImpl) mMenu.getItem(i), 0); 294 mPresenter.setUpdateSuspended(false); 295 } 296 } 297 298 private void activateNewButton(int newButton) { 299 if (mActiveButton == newButton) return; 300 301 mAnimationHelper.beginDelayedTransition(this); 302 303 mMenu.getItem(newButton).setChecked(true); 304 305 mActiveButton = newButton; 306 } 307 308 private BottomNavigationItemView getNewItem() { 309 BottomNavigationItemView item = sItemPool.acquire(); 310 if (item == null) { 311 item = new BottomNavigationItemView(getContext()); 312 } 313 return item; 314 } 315} 316