Android 底部导航控件实例代码

一、先给大家展示下最终效果

通过以上可以看到,图一是简单的使用,图二、图三中为结合ViewPager共同使用,而且都可以随ViewPager的滑动渐变色,不同点是图二为选中非选中两张图片,图三的选中非选中是一张图片只是做了颜色变化。

二、 需求

我们希望做可以做成这样的,可以在xml布局中引入控件并绑定数据,在代码中设置监听回调,并且配置使用要非常简单!

三、需求分析

根据我们多年做不明确需求项目的经验,以上需求还算明确。那么我们可以采用在LinearLayout添加子View控件,这个子View控件就是我们自定义的每个tab条目,当然对LinearLayout要设置权重。

需求大致明确之后就先设计每个条目的子View控件,这个子View控件是一个可以切换状态变化的,一张、两张都可以切换状态(参考图一、图三)。那么这个View要可以设置底部显示的文字,设置选中时颜色、未选中时颜色、选中时图片、未选中时图片、文字大小、设置是否有指示点、设置指示点大小、设置指示点图片等等。

四、Tab条目接口

通过需求分析,我们可以定义如下的Tab子View操作接口:

仔细的朋友会发现,为什么在接口中没有设置选中图片以及设置非选中时图片,那是因为这个属性不是通用的,在不同的实现中再去定义。

五、Tab条目实现

类的继承关系及说明:

类图如下所示:

在TabViewBase中主要的方法就是测量,其他的都是对接口的简单实现。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 得到绘制icon的宽
int bitmapWidth = Math.min(getMeasuredWidth() - getPaddingLeft()
- getPaddingRight(), getMeasuredHeight() - getPaddingTop()
- getPaddingBottom() - mTextBound.height());
int left = getMeasuredWidth() / 2 - bitmapWidth / 2;
int top = (getMeasuredHeight() - mTextBound.height()) / 2 - bitmapWidth / 2;
// 设置icon的绘制范围
mIconRect = new Rect(left, top, left + bitmapWidth, top + bitmapWidth);
// 设置指示点的范围
int indicatorRadius = mIndicatorSize / 2;
int tabRealHeight = bitmapWidth + mTextBound.height();
mIndicatorRect = new Rect(left + tabRealHeight* 4/5 - indicatorRadius, top, left+tabRealHeight* 4/5 + indicatorRadius, top + mIndicatorSize);
} 

在以上代码中可以看到,测量文字的高度,用控件的高度减去文字的高度和控件的宽度对比,取较小的为图片的大小,也就是设置的图片要为正方形,否则会产生变形。

看下普通两张图片切换的TabView的绘制:

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
setupTargetBitmap(canvas);
drawIndicator(canvas);
if(null != mText) {
drawTargetText(canvas);
}
}
/**
* 绘制图标图片
* @param canvas
*/
private void setupTargetBitmap(Canvas canvas) {
canvas.drawBitmap(isSelected ? mSelectedIconBitmap : mUnselectedIconBitmap, null, mIconRect, null);
}
/**
* 绘制指示点
* @param canvas
*/
protected void drawIndicator(Canvas canvas) {
if(isIndicateDisplay) {
canvas.drawBitmap(mIndicatorBitmap, null, mIndicatorRect, null);
}
}
/**
* 绘制文字
* @param canvas
*/
protected void drawTargetText(Canvas canvas) {
mTextPaint.setColor(isSelected ? mSelectedColor : mUnselectedColor);
canvas.drawText(mText, mIconRect.left + mIconRect.width() / 2
- mTextBound.width() / 2,
mIconRect.bottom + mTextBound.height(), mTextPaint);
}

可以看到非常的简单,就是绘制图标图片以及绘制指示点,在绘制图标图片时判断当前条目是否在选中状态,根据是否选中来绘制不同的图片,在绘制指示点的时候首先判断下是否设置了显示指示点。如果有底部文字,那么久绘制底部文字。
在ViewPager两张图片图片的时,我们再把效果图拿过来观察下:

这里有两种模式,即随着ViewPager的滚动图标渐变及普通变化。OK,了解之后我们就能很轻松的来编写它的绘制了,可以通过绘制两张图片,但是在绘制的时候控制它的透明度就可以啦,是不是也很简单。

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int alpha = (int) Math.ceil((255 * mAlpha));
drawSourceBitmap(canvas, alpha);
drawTargetBitmap(canvas, alpha);
if(null != mText) {
drawSourceText(canvas, alpha);
drawTargetText(canvas, alpha);
}
drawIndicator(canvas);
}
/**
* 绘制未选中图标
* @param canvas
* @param alpha
*/
private void drawSourceBitmap(Canvas canvas, int alpha) {
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setAlpha(255 - alpha);
canvas.drawBitmap(mUnselectedIconBitmap, null, mIconRect, mPaint);
}
/**
* 绘制选中图标
* @param canvas
* @param alpha
*/
private void drawTargetBitmap(Canvas canvas, int alpha) {
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setAlpha(alpha);
canvas.drawBitmap(mSelectedIconBitmap, null, mIconRect, mPaint);
}
/**
* 画未选中文字
* @param canvas
* @param alpha
*/
private void drawSourceText(Canvas canvas, int alpha) {
mTextPaint.setTextSize(mTextSize);
mTextPaint.setColor(mUnselectedColor);
mTextPaint.setAlpha(255 - alpha);
canvas.drawText(mText, mIconRect.left + mIconRect.width() / 2
- mTextBound.width() / 2,
mIconRect.bottom + mTextBound.height(), mTextPaint);
}
/**
* 画选中文字
* @param canvas
* @param alpha
*/
private void drawTargetText(Canvas canvas, int alpha) {
mTextPaint.setColor(mSelectedColor);
mTextPaint.setAlpha(alpha);
canvas.drawText(mText, mIconRect.left + mIconRect.width() / 2
- mTextBound.width() / 2,
mIconRect.bottom + mTextBound.height(), mTextPaint);
}

代码中的mAlpha是ViewPager滚动的百分比,然后分别绘制选中以及未选中的图标和文本,但是绘制的时候设置的透明度不同,这样就会有一个渐变的效果。

在ViewPager单张图片图片的时,我们再把效果图拿过来观察下:

private void setupTargetBitmap(int alpha) {
mBitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
mPaint = new Paint();
mPaint.setColor(mSelectedColor);
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setAlpha(alpha);
mCanvas.drawRect(mIconRect, mPaint);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
mPaint.setAlpha(255);
mCanvas.drawBitmap(mIconBitmap, null, mIconRect, mPaint);
}
private void drawSourceText(Canvas canvas, int alpha) {
mTextPaint.setTextSize(mTextSize);
mTextPaint.setColor(mUnselectedColor);
mTextPaint.setAlpha(255 - alpha);
canvas.drawText(mText, mIconRect.left + mIconRect.width() / 2
- mTextBound.width() / 2,
mIconRect.bottom + mTextBound.height(), mTextPaint);
}
private void drawTargetText(Canvas canvas, int alpha) {
mTextPaint.setColor(mSelectedColor);
mTextPaint.setAlpha(alpha);
canvas.drawText(mText, mIconRect.left + mIconRect.width() / 2
- mTextBound.width() / 2,
mIconRect.bottom + mTextBound.height(), mTextPaint);
}

绘制的过程大致与两张图片相同,不同点就是在绘制图片的时候Paint设置 Xfermode,来控制颜色的渐变。

OK,Tab条目的自定义View搞定之后剩下的就简单多了。

六、定义属性

接下来就是封装继承自LinearLayout的整体控件,先来定义下属性。

可以看到tabIcons为单张图片渐变效果特殊的,tabSelectedIcons和tabUnselectedIcon为两张图标切换效果特殊的。

七、 控件编写

由于三中样式有公共的部分,我们进行积累抽取。类图结构如下:

1. 构造函数初始化自定义属性

在TabIndicatorBase中初始化自定义属性

private void init(Context context, AttributeSet attrs) {
setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.CENTER);
//Load defaults from resources
final Resources res = getResources();
final int defaultSelectedColor = res.getColor(R.color.default_tab_view_selected_color);
final int defaultUnselectedColor = res.getColor(R.color.default_tab_view_unselected_color);
final float defaultTextSize = res.getDimension(R.dimen.default_tab_view_text_size);
final float defaultTabPadding = res.getDimension(R.dimen.default_tab_view_padding);
final float defaultIndicatorSize = res.getDimension(R.dimen.default_tab_view_indicator_size);
// Styleables from XML
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabIndicator);
// 读取布局中,各个tab使用的文字
if (a.hasValue(R.styleable.TabIndicator_tabLabels)) {
mLabels = a.getTextArray(R.styleable.TabIndicator_tabLabels);
}
mSelectedColor = a.getColor(R.styleable.TabIndicator_tabSelectedColor, defaultSelectedColor);
mUnselectedColor = a.getColor(R.styleable.TabIndicator_tabUnselectedColor, defaultUnselectedColor);
mTextSize = (int) a.getDimension(R.styleable.TabIndicator_tabTextSize, defaultTextSize);
mIndicatorSize = (int) a.getDimension(R.styleable.TabIndicator_TabIndicatorSize, defaultIndicatorSize);
mTabPadding = (int) a.getDimension(R.styleable.TabIndicator_tabItemPadding, defaultTabPadding);
handleStyledAttributes(a);
a.recycle();
initView();
}

由于有些属性不是公共的,这里定义handleStyleAttributes(a)的抽象方法,在子类中去实现。

2. 初始化View

/**
* 初始化控件
*/
private void initView() {
LayoutParams params = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
params.gravity = Gravity.CENTER;
int size = getTabSize();
for (int i = 0; i < size; i++) {
final int index = i;
T tabItemView = createTabView();
tabItemView.setPadding(mTabPadding, mTabPadding, mTabPadding, mTabPadding);
// 图标及文字
if(null != mLabels) {
tabItemView.setText(mLabels[index]);
tabItemView.setTextSize(mTextSize);
}
tabItemView.setSelectedColor(mSelectedColor);
tabItemView.setUnselectedColor(mUnselectedColor);
tabItemView.setIndicatorSize(mIndicatorSize);
setProperties(tabItemView, i);
this.addView(tabItemView, params);
tabItemView.setTag(index); // CheckedTextView设置索引作为tag,以便后续更改颜色、图片等
mCheckedList.add(tabItemView); // 将CheckedTextView添加到list中,便于操作
tabItemView.setOnClickListener(new OnClickListener()
@Override
public void onClick(View v) {
setTabsDisplay(index); // 设置底部图片和文字的显示
if (null != mTabListener) {
mTabListener.onTabSelected(index); // tab项被选中的回调事件
}
}
});
// 初始化 底部菜单选中状态,默认第一个选中
if (i == 0) {
tabItemView.setSelected(true);
} else {
tabItemView.setSelected(false);
}
}
}

生成Tab条目以及设置特殊的属性都通过抽象方法的方式交给子类去完成。

/**
* 生成TabView
* @return
*/
protected abstract T createTabView();
/**
* 设置特殊属性
* @param t
*/
protected abstract void setProperties(T t, int index); 

3. 子类实现

由于这里都比较简单,我们选取其中一个简单的双图标图片来说明:

@Override
protected void handleStyledAttributes(TypedArray a) {
// 读取布局中,各个tab使用的图标
int selectedIconsResId = a.getResourceId(R.styleable.TabIndicator_tabSelectedIcons, 0);
TypedArray ta = getContext().getResources().obtainTypedArray(selectedIconsResId);
int len = ta.length();
mSelectedDrawableIds = new int[len];
for(int i = 0; i < len; i++) {
mSelectedDrawableIds[i] = ta.getResourceId(i, 0);
}
int unselectedIconsResId = a.getResourceId(R.styleable.TabIndicator_tabUnselectedIcons, 0);
ta = getContext().getResources().obtainTypedArray(unselectedIconsResId);
len = ta.length();
mUnselectedDrawableIds = new int[len];
for(int i = 0; i < len; i++) {
mUnselectedDrawableIds[i] = ta.getResourceId(i, 0);
}
ta.recycle();
}

这里读取了xml中配置的选中及未选中图标

生成TabView

@Override
protected TabView createTabView() {
return new TabView(getContext());
} 

设置特殊属性

@Override
protected void setProperties(TabView tabView, int index) {
tabView.setSelectedIcon(mSelectedDrawableIds[index]);
tabView.setUnselectedIcon(mUnselectedDrawableIds[index]);
} 

获取条目个数

@Override
protected int getTabSize() {
return mSelectedDrawableIds.length;
}

八、源码及示例

给大家提供一个github的地址: Android-TabIndicator
另外,欢迎 star or f**k me on github!

九、一行引入库

如果您的项目使用 Gradle 构建, 只需要在您的build.gradle文件添加下面一行到 dependencies :
compile 'com.kevin:tabindicator:1.0.1'

关于小编给大家分享的Android 底部导航控件实例代码就到此结束了,希望对大家有所帮助!

(0)

相关推荐

  • Android Activity与Fragment实现底部导航器

    单Activity多Fragment实现底部导航器 最近由于Android基础知识讲解需要,采用单Activity多Fragment实现类似QQ底部导航器示例,这种开发模式广泛应用于App开发,比如QQ,微信,新浪等,关于Android底部导航栏的实现方式特别多,实现也是五花八门,同时Google在自己推出的Material design中也增加了Bottom Navigation导航控制,实现起来更加简单,且支持动态效果更加酷炫,但是因为是基础的知识,所以打算通过自定义来实现,不使用Botto

  • Android BottomNavigationView底部导航效果

    BottomNavigationView 很早之前就在 Material Design 中出现了,但是直到 Android Support Library 25 中才增加了 BottomNavigationView 控件.也就是说如果使用官方的BottomNavigationView控件必须让targetSdkVersion >= 25,这样才能引入25版本以上的兼容包. 接下来我们来看看如何使用BottomNavigationView. 使用BottomNavigationView 需要添加d

  • Android BottomNavigationBar底部导航控制器使用方法详解

    最近Google在自己推出的Material design中增加了Bottom Navigation导航控制.Android一直没有官方的导航控制器,自己实现确实是五花八门,有了这个规定之后,就类似苹果的底部Toolbar,以后我们的APP就会有一致的风格,先看一张效果: 这是官方在Material design中给出一张图,确实很不错. 1.BottomNavigationBar的下载地址 https://github.com/Ashok-Varma/BottomNavigation 2.使用

  • android效果TapBarMenu绘制底部导航栏的使用方式示例

    其他的不多说了!我们来看看效果吧       一.实现方式一:直接引入compile方式 Add the dependency to your build.gradle: compile 'com.github.michaldrabik:tapbarmenu:1.0.5' 布局设计 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://sc

  • 关注Ionic底部导航按钮tabs在android情况下浮在上面的处理

    Ionic是一款流行的移动端开发框架,但是刚入门的同学会发现,Ionic在iOS和Android的底部tabs显示不一样.在安卓情况下底部tabs会浮上去. 如下图展示: 网上也有很多此类的解决方案,但是我觉得说一千道一万都不如给个dome实在,下面附上解决方案的dome,大家可以看看! <!DOCTYPE html> <html ng-app="ionic"> <head> <meta charset="UTF-8">

  • Android实现底部导航栏功能(选项卡)

    现在很多android的应用都采用底部导航栏的功能,这样可以使得用户在使用过程中随意切换不同的页面,现在我采用TabHost组件来自定义一个底部的导航栏的功能. 我们先看下该demo实例的框架图: 其中各个类的作用以及资源文件就不详细解释了,还有资源图片(在该Demo中借用了其它应用程序的资源图片)也不提供了,大家可以自行更换自己需要的资源图片.直接上各个布局文件或各个类的代码: 1. res/layout目录下的 maintabs.xml 源码: <?xml version="1.0&q

  • Android程序开发之Fragment实现底部导航栏实例代码

    流行的应用的导航一般分为两种,一种是底部导航,一种是侧边栏. 说明 IDE:AS,Android studio; 模拟器:genymotion; 实现的效果,见下图. 具体实现 为了讲明白这个实现过程,我们贴出来的代码多一写,这样更方便理解 [最后还会放出完整的代码实现] .看上图的界面做的比较粗糙,但实现过程的骨架都具有了,想要更完美的设计,之后自行完善吧 ^0^. 布局 通过观察上述效果图,发现任意一个选项页面都有三部分组成: 顶部去除ActionBar后的标题栏: 中间一个Fragment

  • Android 开发之BottomBar+ViewPager+Fragment实现炫酷的底部导航效果

    BottomBar BottomBar是Github上的一个开源框架,因为从1.3.3开始不支持fragments了,要自己配置,弄了很久,不管是app的fragment还是V4 的程序总是总是闪退.于是就用这种方式实现了,效果还不错.github有详细说明,多余的就不说了. 这个roughike是这个项目的所有者(大神致敬). 我用的是Android studio开发,fragment全部导的V4的包(以为最开始就支持的是v4的,后面也支持了app.fragment). 首先是dependen

  • Android仿微信页面底部导航效果代码实现

    大家在参考本地代码的时候要根据需要适当的修改,里面有冗余代码小编没有删除.好了,废话不多说了,一切让代码说话吧! 关键代码如下所示: .java里面的主要代码 public class MainActivity extends BaseActivity implements TabChangeListener { private Fragment[] fragments; private FragZaiXianYuYue fragZaiXianYuYue; private FragDaoLuJi

  • Android design包自定义tablayout的底部导航栏的实现方法

    以前做项目大多用的radiobutton,今天用tablayout来做一个tab切换页面的的效果. 实现的效果就是类似QQ.微信的页面间(也就是Fragment间)的切换.如图: 布局只要一个tablayout <android.support.design.widget.TabLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:id=&

随机推荐