app的底部菜单栏是非常常见的,微信/qq/支付宝/糯米等都有这样的底部菜单栏,在我们日常的开发过程中也是会经常用到的,下面就是一种实现方式,供大家参考。
首先看下效果图:
自定义底部导航布局BottomNavigationView,代码如下:
package cn.studyou.navigationviewlibrary;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.os.Build;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
public class BottomNavigationView extends RelativeLayout {
private OnBottomNavigationItemClickListener onBottomNavigationItemClickListener;
private Context context;
private final int NAVIGATION_HEIGHT = (int) getResources().getDimension(R.dimen.bottom_navigation_height);
private int SHADOW_HEIGHT;
private int currentItem = 0;
private View backgroundColorTemp;
private boolean withText = true;
private boolean coloredBackground = true;
private int itemActiveColorWithoutColoredBackground = -1;
private int itemInactiveColor;
private FrameLayout container;
private boolean disableShadow = false;
private List bottomNavigationItems = new ArrayList<>();
private List viewList = new ArrayList<>();
private ViewPager mViewPager;
public BottomNavigationView(Context context) {
super(context);
this.context = context;
}
public BottomNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
}
public BottomNavigationView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
ViewGroup.LayoutParams params = getLayoutParams();
if (coloredBackground) {
itemActiveColorWithoutColoredBackground = ContextCompat.getColor(context, R.color.colorActive);
itemInactiveColor = ContextCompat.getColor(context, R.color.colorInactive);
SHADOW_HEIGHT = (int) getResources().getDimension(R.dimen.bottom_navigation_shadow_height);
} else {
if (itemActiveColorWithoutColoredBackground == -1)
itemActiveColorWithoutColoredBackground = ContextCompat.getColor(context, R.color.itemActiveColorWithoutColoredBackground);
itemInactiveColor = ContextCompat.getColor(context, R.color.withoutColoredBackground);
SHADOW_HEIGHT = (int) getResources().getDimension(R.dimen.bottom_navigation_shadow_height_without_colored_background);
}
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
params.height = disableShadow ? NAVIGATION_HEIGHT : NAVIGATION_HEIGHT + SHADOW_HEIGHT;
//setOrientation(LinearLayout.VERTICAL);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setElevation(getResources().getDimension(R.dimen.bottom_navigation_elevation));
}
setLayoutParams(params);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (bottomNavigationItems.size() == 0) {
throw new NullPointerException("You need at least one item");
}
int white = ContextCompat.getColor(context, R.color.white);
backgroundColorTemp = new View(context);
viewList.clear();
int itemWidth = getWidth() / bottomNavigationItems.size();
int itemHeight = LayoutParams.MATCH_PARENT;
container = new FrameLayout(context);
View shadow = new View(context);
LinearLayout items = new LinearLayout(context);
items.setOrientation(LinearLayout.HORIZONTAL);
LayoutParams containerParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, NAVIGATION_HEIGHT);
LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, NAVIGATION_HEIGHT);
LayoutParams shadowParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, SHADOW_HEIGHT);
containerParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
shadowParams.addRule(RelativeLayout.ABOVE, container.getId());
shadow.setBackgroundResource(R.drawable.shadow);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
LayoutParams backgroundLayoutParams = new LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, NAVIGATION_HEIGHT);
backgroundLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
container.addView(backgroundColorTemp, backgroundLayoutParams);
}
addView(shadow, shadowParams);
addView(container, containerParams);
container.addView(items, params);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
for (int i = 0; i < bottomNavigationItems.size(); i++) {
final int index = i;
if (!coloredBackground)
bottomNavigationItems.get(i).setColor(white);
int textActivePaddingTop = (int) context.getResources().getDimension(R.dimen.bottom_navigation_padding_top_active);
int viewInactivePaddingTop = (int) context.getResources().getDimension(R.dimen.bottom_navigation_padding_top_inactive);
int viewInactivePaddingTopWithoutText = (int) context.getResources().getDimension(R.dimen.bottom_navigation_padding_top_inactive_without_text);
final View view = inflater.inflate(R.layout.bottom_navigation, this, false);
ImageView icon = (ImageView) view.findViewById(R.id.bottom_navigation_item_icon);
TextView title = (TextView) view.findViewById(R.id.bottom_navigation_item_title);
title.setTextColor(itemInactiveColor);
viewList.add(view);
if (i == currentItem) {
container.setBackgroundColor(bottomNavigationItems.get(index).getColor());
title.setTextColor(currentItem == i ?
itemActiveColorWithoutColoredBackground :
itemInactiveColor);
}
view.setPadding(view.getPaddingLeft(), i == 0 ? textActivePaddingTop : withText ? viewInactivePaddingTop : viewInactivePaddingTopWithoutText, view.getPaddingRight(),
view.getPaddingBottom());
icon.setImageResource(bottomNavigationItems.get(i).getImageResource());
icon.setColorFilter(i == 0 ? itemActiveColorWithoutColoredBackground : itemInactiveColor);
if (i == 0) {
icon.setScaleX((float) 1.1);
icon.setScaleY((float) 1.1);
}
title.setTextSize(TypedValue.COMPLEX_UNIT_PX, currentItem == i ?
context.getResources().getDimension(R.dimen.bottom_navigation_text_size_active) :
withText ? context.getResources().getDimension(R.dimen.bottom_navigation_text_size_inactive) : 0);
title.setText(bottomNavigationItems.get(i).getTitle());
LayoutParams itemParams = new LayoutParams(itemWidth, itemHeight);
items.addView(view, itemParams);
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
onBottomNavigationItemClick(index);
}
});
}
}
/**
* Add item for BottomNavigation
*
* @param item item to add
*/
public void addTab(BottomNavigationItem item) {
bottomNavigationItems.add(item);
}
public void isWithText(boolean withText) {
this.withText = withText;
}
public void setItemActiveColorWithoutColoredBackground(int itemActiveColorWithoutColoredBackground) {
this.itemActiveColorWithoutColoredBackground = itemActiveColorWithoutColoredBackground;
}
/**
* With this BottomNavigation background will be white
*
* @param coloredBackground disable or enable background color
*/
public void isColoredBackground(boolean coloredBackground) {
this.coloredBackground = coloredBackground;
}
/**
* Disable shadow of BottomNavigationView
*/
public void disableShadow() {
disableShadow = true;
}
private void onBottomNavigationItemClick(final int itemIndex) {
if (currentItem == itemIndex) {
return;
}
int viewActivePaddingTop = (int) context.getResources().getDimension(R.dimen.bottom_navigation_padding_top_active);
int viewInactivePaddingTop = (int) context.getResources().getDimension(R.dimen.bottom_navigation_padding_top_inactive);
int viewInactivePaddingTopWithoutText = (int) context.getResources().getDimension(R.dimen.bottom_navigation_padding_top_inactive_without_text);
float textActiveSize = context.getResources().getDimension(R.dimen.bottom_navigation_text_size_active);
float textInactiveSize = context.getResources().getDimension(R.dimen.bottom_navigation_text_size_inactive);
for (int i = 0; i < viewList.size(); i++) {
if (i == itemIndex) {
View view = viewList.get(itemIndex).findViewById(R.id.bottom_navigation_container);
final TextView title = (TextView) view.findViewById(R.id.bottom_navigation_item_title);
final ImageView icon = (ImageView) view.findViewById(R.id.bottom_navigation_item_icon);
BottomNavigationUtils.changeTextColor(title, itemInactiveColor, itemActiveColorWithoutColoredBackground);
BottomNavigationUtils.changeTextSize(title, withText ? textInactiveSize : 0, textActiveSize);
BottomNavigationUtils.imageColorChange(icon, itemInactiveColor, itemActiveColorWithoutColoredBackground);
BottomNavigationUtils.changeTopPadding(view, withText ? viewInactivePaddingTop : viewInactivePaddingTopWithoutText, viewActivePaddingTop);
icon.animate()
.setDuration(150)
.scaleX((float) 1.1)
.scaleY((float) 1.1)
.start();
int centerX = (int) viewList.get(itemIndex).getX() + viewList.get(itemIndex).getWidth() / 2;
int centerY = viewList.get(itemIndex).getHeight() / 2;
int finalRadius = Math.max(getWidth(), getHeight());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
backgroundColorTemp.setBackgroundColor(bottomNavigationItems.get(itemIndex).getColor());
Animator changeBackgroundColor = ViewAnimationUtils.createCircularReveal(backgroundColorTemp, centerX, centerY, 0, finalRadius);
changeBackgroundColor.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
container.setBackgroundColor(bottomNavigationItems.get(itemIndex).getColor());
}
});
changeBackgroundColor.start();
} else {
BottomNavigationUtils.backgroundColorChange
(container, bottomNavigationItems.get(currentItem).getColor(), bottomNavigationItems.get(itemIndex).getColor());
}
} else if (i == currentItem) {
View view = viewList.get(i).findViewById(R.id.bottom_navigation_container);
final TextView title = (TextView) view.findViewById(R.id.bottom_navigation_item_title);
final ImageView icon = (ImageView) view.findViewById(R.id.bottom_navigation_item_icon);
BottomNavigationUtils.imageColorChange(icon, itemActiveColorWithoutColoredBackground, itemInactiveColor);
BottomNavigationUtils.changeTopPadding(view, viewActivePaddingTop, withText ? viewInactivePaddingTop : viewInactivePaddingTopWithoutText);
BottomNavigationUtils.changeTextColor(title, itemActiveColorWithoutColoredBackground, itemInactiveColor);
BottomNavigationUtils.changeTextSize(title, textActiveSize, withText ? textInactiveSize : 0);
icon.animate()
.setDuration(150)
.scaleX(1)
.scaleY(1)
.start();
}
}
if (mViewPager != null)
mViewPager.setCurrentItem(itemIndex);
if (onBottomNavigationItemClickListener != null)
onBottomNavigationItemClickListener.onNavigationItemClick(itemIndex);
currentItem = itemIndex;
}
/**
* Creates a connection between this navigation view and a ViewPager
*
* @param pager pager to connect to
* @param colorResources color resources for every item in the ViewPager adapter
* @param imageResources images resources for every item in the ViewPager adapter
*/
public void setViewPager(ViewPager pager, int[] colorResources, int[] imageResources) {
this.mViewPager = pager;
if (pager.getAdapter().getCount() != colorResources.length || pager.getAdapter().getCount() != imageResources.length)
throw new IllegalArgumentException("colorResources and imageResources must be equal to the ViewPager items : " + pager.getAdapter().getCount());
for (int i = 0; i < pager.getAdapter().getCount(); i++)
addTab(new BottomNavigationItem(pager.getAdapter().getPageTitle(i).toString(), colorResources[i], imageResources[i]));
mViewPager.addOnPageChangeListener(new internalViewPagerListener());
invalidate();
}
private class internalViewPagerListener implements ViewPager.OnPageChangeListener {
private int mScrollState;
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (mScrollState == ViewPager.SCROLL_STATE_DRAGGING)
onBottomNavigationItemClick(position);
}
@Override
public void onPageSelected(int position) {
onBottomNavigationItemClick(position);
}
@Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_DRAGGING)
mScrollState = ViewPager.SCROLL_STATE_DRAGGING;
else if (state == ViewPager.SCROLL_STATE_IDLE)
mScrollState = ViewPager.SCROLL_STATE_IDLE;
}
}
/**
* Setup interface for item onClick
*/
public interface OnBottomNavigationItemClickListener {
void onNavigationItemClick(int index);
}
public void setOnBottomNavigationItemClickListener(OnBottomNavigationItemClickListener onBottomNavigationItemClickListener) {
this.onBottomNavigationItemClickListener = onBottomNavigationItemClickListener;
}
}
新建底部导航工具类BottomNavigationUtils.java,代码如下:
package cn.studyou.navigationviewlibrary;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.util.TypedValue;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
public class BottomNavigationUtils {
public static void imageColorChange(final ImageView image, int fromColor, int toColor) {
ValueAnimator imageColorChangeAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), fromColor, toColor);
imageColorChangeAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
image.setColorFilter((Integer) animator.getAnimatedValue());
}
});
imageColorChangeAnimation.setDuration(150);
imageColorChangeAnimation.start();
}
public static void backgroundColorChange(final View view, int fromColor, int toColor) {
ValueAnimator imageColorChangeAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), fromColor, toColor);
imageColorChangeAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
view.setBackgroundColor((Integer) animator.getAnimatedValue());
}
});
imageColorChangeAnimation.setDuration(150);
imageColorChangeAnimation.start();
}
public static void changeTopPadding(final View view, int fromPadding, int toPadding) {
ValueAnimator animator = ValueAnimator.ofFloat(fromPadding, toPadding);
animator.setDuration(150);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float animatedValue = (float) valueAnimator.getAnimatedValue();
view.setPadding(view.getPaddingLeft(),
(int) animatedValue,
view.getPaddingRight(),
view.getPaddingBottom());
}
});
animator.start();
}
public static void changeTextSize(final TextView textView, float from, float to) {
ValueAnimator textSizeChangeAnimator = ValueAnimator.ofFloat(from, to);
textSizeChangeAnimator.setDuration(150);
textSizeChangeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, (float) valueAnimator.getAnimatedValue());
}
});
textSizeChangeAnimator.start();
}
public static void changeTextColor(final TextView textView, int fromColor, int toColor) {
ValueAnimator changeTextColorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), fromColor, toColor);
changeTextColorAnimation.setDuration(150);
changeTextColorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
textView.setTextColor((Integer) animator.getAnimatedValue());
}
});
changeTextColorAnimation.start();
}
}
创建实体类BottomNavigationItem.java,代码如下:
package cn.studyou.navigationviewlibrary;
public class BottomNavigationItem {
private String title;
private int color;
private int imageResource;
public BottomNavigationItem(String title, int color, int imageResource) {
this.title = title;
this.color = color;
this.imageResource = imageResource;
}
public int getImageResource() {
return imageResource;
}
public void setImageResource(int imageResource) {
this.imageResource = imageResource;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
新建一张图片shadow.xml,代码如下:
底部导航View的布局文件bottom_navigation.xml,代码如下:
资源文件colors.xml,代码如下:
#2196F3
#FFFFFF
#d2d0d1
#757575
#ffffff
资源文件dimens.xml,代码如下:
8dp
8dp
4dp
104dp
168dp
56dp
24dp
5dp
9dp
16dp
7dp
12dp
12dp
14sp
12sp
到这里我们就定义好了底部菜单栏的View,接下来就可以在我们的Activity中使用了,这里我们采用Activity+Fragment的方式实现布局的切换。
新建MainActivity,如下:
package cn.studyou.bottomnavigation.Activity;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import cn.studyou.bottomnavigation.Fragment.HomeFragment;
import cn.studyou.bottomnavigation.Fragment.MeFragment;
import cn.studyou.bottomnavigation.Fragment.MessageFragment;
import cn.studyou.bottomnavigation.R;
import cn.studyou.navigationviewlibrary.BottomNavigationItem;
import cn.studyou.navigationviewlibrary.BottomNavigationView;
public class MainActivity extends FragmentActivity {
BottomNavigationView bottomNavigationView;
private Fragment homeFragment;
private Fragment meFragment;
private Fragment messageFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottomNavigation);
if (bottomNavigationView != null) {
bottomNavigationView.isWithText(true);
bottomNavigationView.isColoredBackground(true);
//bottomNavigationView.disableShadow();
bottomNavigationView.isColoredBackground(false);
bottomNavigationView.setItemActiveColorWithoutColoredBackground(getResources().getColor(R.color.fourthColor));
}
BottomNavigationItem bottomNavigationItem = new BottomNavigationItem
(getString(R.string.home), getResources().getColor(R.color.firstColor), R.drawable.ic_home_24dp);
BottomNavigationItem bottomNavigationItem1 = new BottomNavigationItem
(getString(R.string.message), getResources().getColor(R.color.secondColor), R.drawable.ic_markunread_24dp);
BottomNavigationItem bottomNavigationItem2 = new BottomNavigationItem
(getString(R.string.me), getResources().getColor(R.color.fourthColor), R.drawable.ic_timer_auto_24dp);
selectedImages(0);
bottomNavigationView.addTab(bottomNavigationItem);
bottomNavigationView.addTab(bottomNavigationItem1);
bottomNavigationView.addTab(bottomNavigationItem2);
bottomNavigationView.setOnBottomNavigationItemClickListener(new BottomNavigationView.OnBottomNavigationItemClickListener() {
@Override
public void onNavigationItemClick(int index) {
switch (index) {
case 0:
selectedImages(0);
break;
case 1:
selectedImages(1);
break;
case 2:
selectedImages(2);
break;
}
}
});
}
/**
* 设置选中
*
* @param i
*/
private void selectedImages(int i) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
hideFragment(fragmentTransaction);
switch (i) {
case 0:
if (homeFragment == null) {
homeFragment = new HomeFragment();
fragmentTransaction.add(R.id.fragment_navigation, homeFragment);
} else {
fragmentTransaction.show(homeFragment);
}
break;
case 1:
if (messageFragment == null) {
messageFragment = new MessageFragment();
fragmentTransaction.add(R.id.fragment_navigation, messageFragment);
} else {
fragmentTransaction.show(messageFragment);
}
break;
case 2:
if (meFragment == null) {
meFragment = new MeFragment();
fragmentTransaction.add(R.id.fragment_navigation, meFragment);
} else {
fragmentTransaction.show(meFragment);
}
break;
default:
break;
}
fragmentTransaction.commit();
}
/**
* 初始化隐藏所有Fragment
*
* @param fragmentTransaction
*/
private void hideFragment(FragmentTransaction fragmentTransaction) {
if (homeFragment != null) {
fragmentTransaction.hide(homeFragment);
}
if (messageFragment != null) {
fragmentTransaction.hide(messageFragment);
}
if (meFragment != null) {
fragmentTransaction.hide(meFragment);
}
}
}
布局文件activity_main.xml 如下:
然后创建Fragment,有几个菜单项就创建几个Fragment,这里我使用了三个菜单项,创建的fragment依次是:HomeFragment、MeFragment、MessageFragment。
package cn.studyou.bottomnavigation.Fragment;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import cn.studyou.bottomnavigation.R;
public class HomeFragment extends Fragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_home, container, false);
}
}
package cn.studyou.bottomnavigation.Fragment;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import cn.studyou.bottomnavigation.R;
public class MeFragment extends Fragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_me, container, false);
}
}
package cn.studyou.bottomnavigation.Fragment;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import cn.studyou.bottomnavigation.R;
public class MessageFragment extends Fragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_message, container, false);
}
}
布局文件也很简单
fragment_home.xml
fragment_me.xml
fragment_message.xml
菜单的图标是使用Android Studio创建的,如下:
ic_home_24dp.xml
ic_markunread_24dp.xml
ic_timer_auto_24dp.xml
最后给出资源文件
strings.xml
BottomNavigation
主页
消息
我的
colors.xml
#3F51B5
#303F9F
#FF4081
#00BCD4
#E91E63
#FF5722
#4CAF50
到这里整个底部菜单栏就完成来,看起来是一大堆的代码,其实很好理解,就是使用一个自定义的布局和Fragment结合来实现的,自定义布局我们都要理解,Fragment使用方式我们更要熟悉。
- 项目源码地址:https://github.com/wjie2014/BottomNavigation