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