blob: 5d7c19d5b06a4c4f839bfe5fa32261c742a837f6 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.design.internal;
import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
import android.support.design.R;
import android.support.v4.util.Pools;
import android.support.v4.view.ViewCompat;
import android.support.v7.view.menu.MenuBuilder;
import android.support.v7.view.menu.MenuItemImpl;
import android.support.v7.view.menu.MenuView;
import android.util.AttributeSet;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
/**
* @hide For internal use only.
*/
@RestrictTo(LIBRARY_GROUP)
public class BottomNavigationMenuView extends ViewGroup implements MenuView {
private final int mInactiveItemMaxWidth;
private final int mInactiveItemMinWidth;
private final int mActiveItemMaxWidth;
private final int mItemHeight;
private final OnClickListener mOnClickListener;
private final BottomNavigationAnimationHelperBase mAnimationHelper;
private final Pools.Pool<BottomNavigationItemView> mItemPool = new Pools.SynchronizedPool<>(5);
private boolean mShiftingMode = true;
private BottomNavigationItemView[] mButtons;
private int mSelectedItemId = 0;
private int mSelectedItemPosition = 0;
private ColorStateList mItemIconTint;
private ColorStateList mItemTextColor;
private int mItemBackgroundRes;
private int[] mTempChildWidths;
private BottomNavigationPresenter mPresenter;
private MenuBuilder mMenu;
public BottomNavigationMenuView(Context context) {
this(context, null);
}
public BottomNavigationMenuView(Context context, AttributeSet attrs) {
super(context, attrs);
final Resources res = getResources();
mInactiveItemMaxWidth = res.getDimensionPixelSize(
R.dimen.design_bottom_navigation_item_max_width);
mInactiveItemMinWidth = res.getDimensionPixelSize(
R.dimen.design_bottom_navigation_item_min_width);
mActiveItemMaxWidth = res.getDimensionPixelSize(
R.dimen.design_bottom_navigation_active_item_max_width);
mItemHeight = res.getDimensionPixelSize(R.dimen.design_bottom_navigation_height);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
mAnimationHelper = new BottomNavigationAnimationHelperIcs();
} else {
mAnimationHelper = new BottomNavigationAnimationHelperBase();
}
mOnClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
final BottomNavigationItemView itemView = (BottomNavigationItemView) v;
MenuItem item = itemView.getItemData();
if (!mMenu.performItemAction(item, mPresenter, 0)) {
item.setChecked(true);
}
}
};
mTempChildWidths = new int[BottomNavigationMenu.MAX_ITEM_COUNT];
}
@Override
public void initialize(MenuBuilder menu) {
mMenu = menu;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int count = getChildCount();
final int heightSpec = MeasureSpec.makeMeasureSpec(mItemHeight, MeasureSpec.EXACTLY);
if (mShiftingMode) {
final int inactiveCount = count - 1;
final int activeMaxAvailable = width - inactiveCount * mInactiveItemMinWidth;
final int activeWidth = Math.min(activeMaxAvailable, mActiveItemMaxWidth);
final int inactiveMaxAvailable = (width - activeWidth) / inactiveCount;
final int inactiveWidth = Math.min(inactiveMaxAvailable, mInactiveItemMaxWidth);
int extra = width - activeWidth - inactiveWidth * inactiveCount;
for (int i = 0; i < count; i++) {
mTempChildWidths[i] = (i == mSelectedItemPosition) ? activeWidth : inactiveWidth;
if (extra > 0) {
mTempChildWidths[i]++;
extra--;
}
}
} else {
final int maxAvailable = width / (count == 0 ? 1 : count);
final int childWidth = Math.min(maxAvailable, mActiveItemMaxWidth);
int extra = width - childWidth * count;
for (int i = 0; i < count; i++) {
mTempChildWidths[i] = childWidth;
if (extra > 0) {
mTempChildWidths[i]++;
extra--;
}
}
}
int totalWidth = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
child.measure(MeasureSpec.makeMeasureSpec(mTempChildWidths[i], MeasureSpec.EXACTLY),
heightSpec);
ViewGroup.LayoutParams params = child.getLayoutParams();
params.width = child.getMeasuredWidth();
totalWidth += child.getMeasuredWidth();
}
setMeasuredDimension(
ViewCompat.resolveSizeAndState(totalWidth,
MeasureSpec.makeMeasureSpec(totalWidth, MeasureSpec.EXACTLY), 0),
ViewCompat.resolveSizeAndState(mItemHeight, heightSpec, 0));
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int count = getChildCount();
final int width = right - left;
final int height = bottom - top;
int used = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL) {
child.layout(width - used - child.getMeasuredWidth(), 0, width - used, height);
} else {
child.layout(used, 0, child.getMeasuredWidth() + used, height);
}
used += child.getMeasuredWidth();
}
}
@Override
public int getWindowAnimations() {
return 0;
}
/**
* Sets the tint which is applied to the menu items' icons.
*
* @param tint the tint to apply
*/
public void setIconTintList(ColorStateList tint) {
mItemIconTint = tint;
if (mButtons == null) return;
for (BottomNavigationItemView item : mButtons) {
item.setIconTintList(tint);
}
}
/**
* Returns the tint which is applied to menu items' icons.
*
* @return the ColorStateList that is used to tint menu items' icons
*/
@Nullable
public ColorStateList getIconTintList() {
return mItemIconTint;
}
/**
* Sets the text color to be used on menu items.
*
* @param color the ColorStateList used for menu items' text.
*/
public void setItemTextColor(ColorStateList color) {
mItemTextColor = color;
if (mButtons == null) return;
for (BottomNavigationItemView item : mButtons) {
item.setTextColor(color);
}
}
/**
* Returns the text color used on menu items.
*
* @return the ColorStateList used for menu items' text
*/
public ColorStateList getItemTextColor() {
return mItemTextColor;
}
/**
* Sets the resource ID to be used for item background.
*
* @param background the resource ID of the background
*/
public void setItemBackgroundRes(int background) {
mItemBackgroundRes = background;
if (mButtons == null) return;
for (BottomNavigationItemView item : mButtons) {
item.setItemBackground(background);
}
}
/**
* Returns the resource ID for the background of the menu items.
*
* @return the resource ID for the background
*/
public int getItemBackgroundRes() {
return mItemBackgroundRes;
}
public void setPresenter(BottomNavigationPresenter presenter) {
mPresenter = presenter;
}
public void buildMenuView() {
removeAllViews();
if (mButtons != null) {
for (BottomNavigationItemView item : mButtons) {
mItemPool.release(item);
}
}
if (mMenu.size() == 0) {
mSelectedItemId = 0;
mSelectedItemPosition = 0;
mButtons = null;
return;
}
mButtons = new BottomNavigationItemView[mMenu.size()];
mShiftingMode = mMenu.size() > 3;
for (int i = 0; i < mMenu.size(); i++) {
mPresenter.setUpdateSuspended(true);
mMenu.getItem(i).setCheckable(true);
mPresenter.setUpdateSuspended(false);
BottomNavigationItemView child = getNewItem();
mButtons[i] = child;
child.setIconTintList(mItemIconTint);
child.setTextColor(mItemTextColor);
child.setItemBackground(mItemBackgroundRes);
child.setShiftingMode(mShiftingMode);
child.initialize((MenuItemImpl) mMenu.getItem(i), 0);
child.setItemPosition(i);
child.setOnClickListener(mOnClickListener);
addView(child);
}
mSelectedItemPosition = Math.min(mMenu.size() - 1, mSelectedItemPosition);
mMenu.getItem(mSelectedItemPosition).setChecked(true);
}
public void updateMenuView() {
final int menuSize = mMenu.size();
if (menuSize != mButtons.length) {
// The size has changed. Rebuild menu view from scratch.
buildMenuView();
return;
}
int previousSelectedId = mSelectedItemId;
for (int i = 0; i < menuSize; i++) {
mPresenter.setUpdateSuspended(true);
MenuItem item = mMenu.getItem(i);
if (item.isChecked()) {
mSelectedItemId = item.getItemId();
mSelectedItemPosition = i;
}
mButtons[i].initialize((MenuItemImpl) item, 0);
mPresenter.setUpdateSuspended(false);
}
if (previousSelectedId != mSelectedItemId) {
mAnimationHelper.beginDelayedTransition(this);
}
}
private BottomNavigationItemView getNewItem() {
BottomNavigationItemView item = mItemPool.acquire();
if (item == null) {
item = new BottomNavigationItemView(getContext());
}
return item;
}
public int getSelectedItemId() {
return mSelectedItemId;
}
void tryRestoreSelectedItemId(int itemId) {
final int size = mMenu.size();
for (int i = 0; i < size; i++) {
MenuItem item = mMenu.getItem(i);
if (itemId == item.getItemId()) {
mSelectedItemId = itemId;
mSelectedItemPosition = i;
item.setChecked(true);
break;
}
}
}
}