BottomNavigationMenuView.java revision cf41f511604d2ba0234f619dcf5a944947a24d0d
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.LIBRARY_GROUP; 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.MenuItem; 35import android.view.View; 36import android.view.ViewGroup; 37 38/** 39 * @hide For internal use only. 40 */ 41@RestrictTo(LIBRARY_GROUP) 42public class BottomNavigationMenuView extends ViewGroup implements MenuView { 43 private final int mInactiveItemMaxWidth; 44 private final int mInactiveItemMinWidth; 45 private final int mActiveItemMaxWidth; 46 private final int mItemHeight; 47 private final OnClickListener mOnClickListener; 48 private final BottomNavigationAnimationHelperBase mAnimationHelper; 49 private final Pools.Pool<BottomNavigationItemView> mItemPool = new Pools.SynchronizedPool<>(5); 50 51 private boolean mShiftingMode = true; 52 53 private BottomNavigationItemView[] mButtons; 54 private int mSelectedItemId = 0; 55 private int mSelectedItemPosition = 0; 56 private ColorStateList mItemIconTint; 57 private ColorStateList mItemTextColor; 58 private int mItemBackgroundRes; 59 private int[] mTempChildWidths; 60 61 private BottomNavigationPresenter mPresenter; 62 private MenuBuilder mMenu; 63 64 public BottomNavigationMenuView(Context context) { 65 this(context, null); 66 } 67 68 public BottomNavigationMenuView(Context context, AttributeSet attrs) { 69 super(context, attrs); 70 final Resources res = getResources(); 71 mInactiveItemMaxWidth = res.getDimensionPixelSize( 72 R.dimen.design_bottom_navigation_item_max_width); 73 mInactiveItemMinWidth = res.getDimensionPixelSize( 74 R.dimen.design_bottom_navigation_item_min_width); 75 mActiveItemMaxWidth = res.getDimensionPixelSize( 76 R.dimen.design_bottom_navigation_active_item_max_width); 77 mItemHeight = res.getDimensionPixelSize(R.dimen.design_bottom_navigation_height); 78 79 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 80 mAnimationHelper = new BottomNavigationAnimationHelperIcs(); 81 } else { 82 mAnimationHelper = new BottomNavigationAnimationHelperBase(); 83 } 84 85 mOnClickListener = new OnClickListener() { 86 @Override 87 public void onClick(View v) { 88 final BottomNavigationItemView itemView = (BottomNavigationItemView) v; 89 MenuItem item = itemView.getItemData(); 90 if (!mMenu.performItemAction(item, mPresenter, 0)) { 91 item.setChecked(true); 92 } 93 } 94 }; 95 mTempChildWidths = new int[BottomNavigationMenu.MAX_ITEM_COUNT]; 96 } 97 98 @Override 99 public void initialize(MenuBuilder menu) { 100 mMenu = menu; 101 } 102 103 @Override 104 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 105 final int width = MeasureSpec.getSize(widthMeasureSpec); 106 final int count = getChildCount(); 107 108 final int heightSpec = MeasureSpec.makeMeasureSpec(mItemHeight, MeasureSpec.EXACTLY); 109 110 if (mShiftingMode) { 111 final int inactiveCount = count - 1; 112 final int activeMaxAvailable = width - inactiveCount * mInactiveItemMinWidth; 113 final int activeWidth = Math.min(activeMaxAvailable, mActiveItemMaxWidth); 114 final int inactiveMaxAvailable = (width - activeWidth) / inactiveCount; 115 final int inactiveWidth = Math.min(inactiveMaxAvailable, mInactiveItemMaxWidth); 116 int extra = width - activeWidth - inactiveWidth * inactiveCount; 117 for (int i = 0; i < count; i++) { 118 mTempChildWidths[i] = (i == mSelectedItemPosition) ? activeWidth : inactiveWidth; 119 if (extra > 0) { 120 mTempChildWidths[i]++; 121 extra--; 122 } 123 } 124 } else { 125 final int maxAvailable = width / (count == 0 ? 1 : count); 126 final int childWidth = Math.min(maxAvailable, mActiveItemMaxWidth); 127 int extra = width - childWidth * count; 128 for (int i = 0; i < count; i++) { 129 mTempChildWidths[i] = childWidth; 130 if (extra > 0) { 131 mTempChildWidths[i]++; 132 extra--; 133 } 134 } 135 } 136 137 int totalWidth = 0; 138 for (int i = 0; i < count; i++) { 139 final View child = getChildAt(i); 140 if (child.getVisibility() == GONE) { 141 continue; 142 } 143 child.measure(MeasureSpec.makeMeasureSpec(mTempChildWidths[i], MeasureSpec.EXACTLY), 144 heightSpec); 145 ViewGroup.LayoutParams params = child.getLayoutParams(); 146 params.width = child.getMeasuredWidth(); 147 totalWidth += child.getMeasuredWidth(); 148 } 149 setMeasuredDimension( 150 ViewCompat.resolveSizeAndState(totalWidth, 151 MeasureSpec.makeMeasureSpec(totalWidth, MeasureSpec.EXACTLY), 0), 152 ViewCompat.resolveSizeAndState(mItemHeight, heightSpec, 0)); 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 /** 181 * Sets the tint which is applied to the menu items' icons. 182 * 183 * @param tint the tint to apply 184 */ 185 public void setIconTintList(ColorStateList tint) { 186 mItemIconTint = tint; 187 if (mButtons == null) return; 188 for (BottomNavigationItemView item : mButtons) { 189 item.setIconTintList(tint); 190 } 191 } 192 193 /** 194 * Returns the tint which is applied to menu items' icons. 195 * 196 * @return the ColorStateList that is used to tint menu items' icons 197 */ 198 @Nullable 199 public ColorStateList getIconTintList() { 200 return mItemIconTint; 201 } 202 203 /** 204 * Sets the text color to be used on menu items. 205 * 206 * @param color the ColorStateList used for menu items' text. 207 */ 208 public void setItemTextColor(ColorStateList color) { 209 mItemTextColor = color; 210 if (mButtons == null) return; 211 for (BottomNavigationItemView item : mButtons) { 212 item.setTextColor(color); 213 } 214 } 215 216 /** 217 * Returns the text color used on menu items. 218 * 219 * @return the ColorStateList used for menu items' text 220 */ 221 public ColorStateList getItemTextColor() { 222 return mItemTextColor; 223 } 224 225 /** 226 * Sets the resource ID to be used for item background. 227 * 228 * @param background the resource ID of the background 229 */ 230 public void setItemBackgroundRes(int background) { 231 mItemBackgroundRes = background; 232 if (mButtons == null) return; 233 for (BottomNavigationItemView item : mButtons) { 234 item.setItemBackground(background); 235 } 236 } 237 238 /** 239 * Returns the resource ID for the background of the menu items. 240 * 241 * @return the resource ID for the background 242 */ 243 public int getItemBackgroundRes() { 244 return mItemBackgroundRes; 245 } 246 247 public void setPresenter(BottomNavigationPresenter presenter) { 248 mPresenter = presenter; 249 } 250 251 public void buildMenuView() { 252 removeAllViews(); 253 if (mButtons != null) { 254 for (BottomNavigationItemView item : mButtons) { 255 mItemPool.release(item); 256 } 257 } 258 if (mMenu.size() == 0) { 259 mSelectedItemId = 0; 260 mSelectedItemPosition = 0; 261 mButtons = null; 262 return; 263 } 264 mButtons = new BottomNavigationItemView[mMenu.size()]; 265 mShiftingMode = mMenu.size() > 3; 266 for (int i = 0; i < mMenu.size(); i++) { 267 mPresenter.setUpdateSuspended(true); 268 mMenu.getItem(i).setCheckable(true); 269 mPresenter.setUpdateSuspended(false); 270 BottomNavigationItemView child = getNewItem(); 271 mButtons[i] = child; 272 child.setIconTintList(mItemIconTint); 273 child.setTextColor(mItemTextColor); 274 child.setItemBackground(mItemBackgroundRes); 275 child.setShiftingMode(mShiftingMode); 276 child.initialize((MenuItemImpl) mMenu.getItem(i), 0); 277 child.setItemPosition(i); 278 child.setOnClickListener(mOnClickListener); 279 addView(child); 280 } 281 mSelectedItemPosition = Math.min(mMenu.size() - 1, mSelectedItemPosition); 282 mMenu.getItem(mSelectedItemPosition).setChecked(true); 283 } 284 285 public void updateMenuView() { 286 final int menuSize = mMenu.size(); 287 if (menuSize != mButtons.length) { 288 // The size has changed. Rebuild menu view from scratch. 289 buildMenuView(); 290 return; 291 } 292 int previousSelectedId = mSelectedItemId; 293 294 for (int i = 0; i < menuSize; i++) { 295 MenuItem item = mMenu.getItem(i); 296 if (item.isChecked()) { 297 mSelectedItemId = item.getItemId(); 298 mSelectedItemPosition = i; 299 } 300 } 301 if (previousSelectedId != mSelectedItemId) { 302 // Note: this has to be called before BottomNavigationItemView#initialize(). 303 mAnimationHelper.beginDelayedTransition(this); 304 } 305 306 for (int i = 0; i < menuSize; i++) { 307 mPresenter.setUpdateSuspended(true); 308 mButtons[i].initialize((MenuItemImpl) mMenu.getItem(i), 0); 309 mPresenter.setUpdateSuspended(false); 310 } 311 312 } 313 314 private BottomNavigationItemView getNewItem() { 315 BottomNavigationItemView item = mItemPool.acquire(); 316 if (item == null) { 317 item = new BottomNavigationItemView(getContext()); 318 } 319 return item; 320 } 321 322 public int getSelectedItemId() { 323 return mSelectedItemId; 324 } 325 326 void tryRestoreSelectedItemId(int itemId) { 327 final int size = mMenu.size(); 328 for (int i = 0; i < size; i++) { 329 MenuItem item = mMenu.getItem(i); 330 if (itemId == item.getItemId()) { 331 mSelectedItemId = itemId; 332 mSelectedItemPosition = i; 333 item.setChecked(true); 334 break; 335 } 336 } 337 } 338} 339