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 底部导航控件实例代码就到此结束了,希望对大家有所帮助!

时间: 2024-09-02 00:09:36

Android 底部导航控件实例代码的相关文章

Android 底部导航控件实例代码_Android

一.先给大家展示下最终效果 通过以上可以看到,图一是简单的使用,图二.图三中为结合ViewPager共同使用,而且都可以随ViewPager的滑动渐变色,不同点是图二为选中非选中两张图片,图三的选中非选中是一张图片只是做了颜色变化. 二. 需求 我们希望做可以做成这样的,可以在xml布局中引入控件并绑定数据,在代码中设置监听回调,并且配置使用要非常简单! 三.需求分析 根据我们多年做不明确需求项目的经验,以上需求还算明确.那么我们可以采用在LinearLayout添加子View控件,这个子Vie

Android开发中DatePicker日期与时间控件实例代码

一.简介 二.方法 最日常的使用方法了 日期控件DatePicker 时间控件TimePicker 月份从0开始 三.代码实例 效果图: 代码: fry.Activity01 package fry; import com.example.DatePicherDemo1.R; import android.app.Activity; import android.os.Bundle; import android.widget.DatePicker; import android.widget.

Android实现滑动选择控件实例代码

前言 最近做了个滑动选择的小控件,拿出来给大家分享一下,先上图 运行效果 实现步骤 这里分解为3个动作:Down.Move.Up来进行分析,博主文采不好,大家直接看流程图吧!! 代码分析 前置知识 1.这个地方使用的是RecyclerView的代码,使用RecyclerView只能使用LinearLayoutManager,ListView的运行效果稍微要比RecyclerView差一些 //这里使用dispatchTouchEvent,因为onTouchEvent容易被OnTouchListe

Android自定义日历控件实例详解_Android

为什么要自定义控件 有时,原生控件不能满足我们对于外观和功能的需求,这时候可以自定义控件来定制外观或功能:有时,原生控件可以通过复杂的编码实现想要的功能,这时候可以自定义控件来提高代码的可复用性. 如何自定义控件 下面我通过我在github上开源的Android-CalendarView项目为例,来介绍一下自定义控件的方法.该项目中自定义的控件类名是CalendarView.这个自定义控件覆盖了一些自定义控件时常需要重写的一些方法. 构造函数 为了支持本控件既能使用xml布局文件声明,也可在ja

Android 自定义 view 控件实例

Android自定义view通过继承系统的View并重写部分方法来满足自己的特定需要.首先我们来看一下都有哪些方法可能需要被重写: onMeasure() 检测View组件及其子组件的大小 onLayout() 当该组件需要分配其子组件的位置.大小时 onTouchEvent 当发生触屏事件时 onDraw() 当组件将要绘制它的内容时 onKeyDown 当按下某个键盘时 onKeyUp  当松开某个键盘时 onTrackballEvent 当发生轨迹球事件时 onSizeChange() 当

Android实现日历控件示例代码

做的是一个酒店的项目,可以选择入住和离开的日期.声明为了省事在网上找的资料,自己修改的逻辑,希望对需要的朋友有帮助.喜欢的给个好评.谢谢啦!祝生活愉快! 先上图 第一步,搭建布局xml <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_w

安卓自定义流程进度图控件实例代码

先上效果图: 如图,可实现设置:总流程数.已完进度程数.已完成颜色,各个标题 github地址戳这里 使用方法 1.导入compile 'com.github.pavlospt:circleview:1.3'依赖包(因为用到了CircleView) 2.直接把下面两个文件一个java一个xml,复制粘贴进项目(代码放在了文章最后,暂时还没弄成开源库,有时间直接做成依赖包倒进去) 在xml中写入ProcessImg控件 在java文件中实例化ProcessImg对象 根据需要调用几个方法 1.对象

jquery动态增加删除控件实例代码

可以添加在增减过程中需要的自定义参数和变量,如: $("table").dynamicaddremover({param1:"param1", param2:"param2"}); 使用时,如:options.param1等等. .根容器必须有唯一id且指定时必须唯一.如果有多个模块(多个根容器)需要使用可以这样:$("#table1").dynamicaddremover(),$("#table2").d

Android自定义日历控件实例详解

为什么要自定义控件 有时,原生控件不能满足我们对于外观和功能的需求,这时候可以自定义控件来定制外观或功能:有时,原生控件可以通过复杂的编码实现想要的功能,这时候可以自定义控件来提高代码的可复用性. 如何自定义控件 下面我通过我在github上开源的Android-CalendarView项目为例,来介绍一下自定义控件的方法.该项目中自定义的控件类名是CalendarView.这个自定义控件覆盖了一些自定义控件时常需要重写的一些方法. 构造函数 为了支持本控件既能使用xml布局文件声明,也可在ja