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