BottomNavigationItemView.java revision 560426d9e9d9a9f29ed1a4abb0ee9cf837ed8a4f
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 android.annotation.TargetApi; 20import android.content.Context; 21import android.content.res.ColorStateList; 22import android.graphics.drawable.Drawable; 23import android.os.Build; 24import android.support.annotation.NonNull; 25import android.support.design.R; 26import android.support.v4.content.ContextCompat; 27import android.support.v4.graphics.drawable.DrawableCompat; 28import android.support.v4.view.ViewCompat; 29import android.support.v4.view.animation.LinearOutSlowInInterpolator; 30import android.support.v7.view.menu.MenuItemImpl; 31import android.support.v7.view.menu.MenuView; 32import android.util.AttributeSet; 33import android.view.LayoutInflater; 34import android.view.ViewPropertyAnimator; 35import android.widget.FrameLayout; 36import android.widget.ImageView; 37import android.widget.TextView; 38 39/** 40 * @hide 41 */ 42public class BottomNavigationItemView extends FrameLayout implements MenuView.ItemView { 43 public static final int INVALID_ITEM_POSITION = -1; 44 45 private static final int[] CHECKED_STATE_SET = { android.R.attr.state_checked }; 46 private static final long ACTIVE_ANIMATION_DURATION_MS = 115L; 47 48 private final float mShiftAmount; 49 private final float mScaleUpFactor; 50 private final float mScaleDownFactor; 51 private final float mInactiveLabelSize; 52 private final float mActiveLabelSize; 53 54 private ImageView mIcon; 55 private final TextView mSmallLabel; 56 private final TextView mLargeLabel; 57 private int mItemPosition = INVALID_ITEM_POSITION; 58 59 private MenuItemImpl mItemData; 60 61 private ColorStateList mIconTint; 62 63 public BottomNavigationItemView(@NonNull Context context) { 64 this(context, null); 65 } 66 67 public BottomNavigationItemView(@NonNull Context context, AttributeSet attrs) { 68 this(context, attrs, 0); 69 } 70 71 public BottomNavigationItemView(Context context, AttributeSet attrs, int defStyleAttr) { 72 super(context, attrs, defStyleAttr); 73 mInactiveLabelSize = 74 getResources().getDimension(R.dimen.design_bottom_navigation_text_size); 75 mActiveLabelSize = 76 getResources().getDimension(R.dimen.design_bottom_navigation_active_text_size); 77 mShiftAmount = mInactiveLabelSize - mActiveLabelSize; 78 mScaleUpFactor = mActiveLabelSize / mInactiveLabelSize; 79 mScaleDownFactor = mInactiveLabelSize / mActiveLabelSize; 80 81 LayoutInflater.from(context).inflate(R.layout.design_bottom_navigation_item, this, true); 82 setBackgroundResource(R.drawable.design_bottom_navigation_item_background); 83 mIcon = (ImageView) findViewById(R.id.icon); 84 mSmallLabel = (TextView) findViewById(R.id.smallLabel); 85 mLargeLabel = (TextView) findViewById(R.id.largeLabel); 86 87 } 88 89 @Override 90 public void initialize(MenuItemImpl itemData, int menuType) { 91 mItemData = itemData; 92 setCheckable(itemData.isCheckable()); 93 setChecked(itemData.isChecked(), false); 94 setEnabled(itemData.isEnabled()); 95 setIcon(itemData.getIcon()); 96 setTitle(itemData.getTitle()); 97 setId(itemData.getItemId()); 98 } 99 100 public void setItemPosition(int position) { 101 mItemPosition = position; 102 } 103 104 public int getItemPosition() { 105 return mItemPosition; 106 } 107 108 @Override 109 public MenuItemImpl getItemData() { 110 return mItemData; 111 } 112 113 @Override 114 public void setTitle(CharSequence title) { 115 mSmallLabel.setText(title); 116 mLargeLabel.setText(title); 117 } 118 119 @Override 120 public void setCheckable(boolean checkable) { 121 refreshDrawableState(); 122 } 123 124 @Override 125 public void setChecked(boolean checked) { 126 setChecked(checked, true); 127 } 128 129 public void setChecked(boolean checked, boolean animate) { 130 mItemData.setChecked(checked); 131 132 if (checked) { 133 mLargeLabel.setVisibility(VISIBLE); 134 mSmallLabel.setVisibility(INVISIBLE); 135 } else { 136 mLargeLabel.setVisibility(INVISIBLE); 137 mSmallLabel.setVisibility(VISIBLE); 138 } 139 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { 140 if (animate) { 141 animate(checked); 142 } else { 143 mIcon.setTranslationY(checked ? mShiftAmount : 0f); 144 } 145 } 146 147 refreshDrawableState(); 148 } 149 150 @Override 151 public void setEnabled(boolean enabled) { 152 super.setEnabled(enabled); 153 mSmallLabel.setEnabled(enabled); 154 mLargeLabel.setEnabled(enabled); 155 mIcon.setEnabled(enabled); 156 } 157 158 @Override 159 public int[] onCreateDrawableState(final int extraSpace) { 160 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 161 if (mItemData != null && mItemData.isCheckable() && mItemData.isChecked()) { 162 mergeDrawableStates(drawableState, CHECKED_STATE_SET); 163 } 164 return drawableState; 165 } 166 167 @Override 168 public void setShortcut(boolean showShortcut, char shortcutKey) { 169 } 170 171 @Override 172 public void setIcon(Drawable icon) { 173 if (icon != null) { 174 Drawable.ConstantState state = icon.getConstantState(); 175 icon = DrawableCompat.wrap(state == null ? icon : state.newDrawable()).mutate(); 176 DrawableCompat.setTintList(icon, mIconTint); 177 } 178 mIcon.setImageDrawable(icon); 179 } 180 181 @Override 182 public boolean prefersCondensedTitle() { 183 return false; 184 } 185 186 @Override 187 public boolean showsIcon() { 188 return true; 189 } 190 191 public void setIconTintList(ColorStateList tint) { 192 mIconTint = tint; 193 if (mItemData != null) { 194 // Update the icon so that the tint takes effect 195 setIcon(mItemData.getIcon()); 196 } 197 } 198 199 public void setTextColor(ColorStateList color) { 200 mSmallLabel.setTextColor(color); 201 mLargeLabel.setTextColor(color); 202 } 203 204 public void setItemBackground(int background) { 205 Drawable backgroundDrawable = background == 0 206 ? null : ContextCompat.getDrawable(getContext(), background); 207 ViewCompat.setBackground(this, backgroundDrawable); 208 } 209 210 @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) 211 private void animate(final boolean active) { 212 // Grow or shrink the text of the tab. 213 final ViewPropertyAnimator textAnimator; 214 if (active) { 215 mLargeLabel.setVisibility(VISIBLE); 216 mSmallLabel.setVisibility(INVISIBLE); 217 textAnimator = scaleLabel(mLargeLabel, active); 218 } else { 219 mLargeLabel.setVisibility(INVISIBLE); 220 mSmallLabel.setVisibility(VISIBLE); 221 textAnimator = scaleLabel(mSmallLabel, active); 222 } 223 224 final ViewPropertyAnimator translationAnimation = mIcon.animate() 225 .setDuration(ACTIVE_ANIMATION_DURATION_MS) 226 .setInterpolator(new LinearOutSlowInInterpolator()) 227 .translationY(active ? mShiftAmount : 0); 228 229 textAnimator.start(); 230 translationAnimation.start(); 231 } 232 233 private ViewPropertyAnimator scaleLabel(TextView label, boolean active) { 234 final float startingTextScale = active ? mScaleDownFactor : mScaleUpFactor; 235 label.setPivotX(label.getWidth() / 2); 236 label.setPivotY(label.getBaseline()); 237 label.setScaleX(startingTextScale); 238 label.setScaleY(startingTextScale); 239 ViewPropertyAnimator textAnimator = label.animate() 240 .setDuration(ACTIVE_ANIMATION_DURATION_MS) 241 .setInterpolator(new LinearOutSlowInInterpolator()) 242 .scaleX(1f) 243 .scaleY(1f); 244 return textAnimator; 245 } 246} 247