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