【Android - 自定义View】之自定义颜色渐变的Tab导航栏
首先来介绍一下这个自定义View:
- (1)这个自定义View的名称叫做 GradientTab ,继承自View类;
- (2)这个自定义View实现了颜色渐变的Tab导航栏(仿微信主菜单),用户在左右滑动的时候,当前页对应的Tab逐渐变淡,目标页的Tab逐渐变深;
- (3)用户可以在XML布局中自定义变色的颜色、图标、文本、文本大小、文本颜色、图文间隔等属性。
接下来简单介绍一下在这个自定义View中用到的技术点:
- (1)自定义属性;
- (2)在 onMeasure() 方法中对View进行测量;
- (3)在 onLayout() 方法中进行一些在获取到测量值之后才能进行的操作,避免代码多次调用影响性能;
- (4)使用 Canvas 、 Paint 、 Bitmap 类对自定义View进行绘制;
- (5)通过 onSaveInstanceState() 和 onRestoreInstanceState() 方法保存和重置View状态(透明度)。
下面是这个自定义View—— GradientTab 的实现代码:
自定义View类 GradientTab.java 中的代码:
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View; /**
* 与ViewPager连用的拖动可渐变色的Tab按钮
*/
public class GradientTab extends View {
private int width, height; // View最终显示的宽高 private int backColor = Color.GREEN; // 自定义属性:Tab按钮的背景颜色
private int spacing = -1; // 自定义属性:Tab按钮中图标和文本的间隔
private StringBuffer text = new StringBuffer(); // 自定义属性:Tab按钮中显示的文本
private int textColor = Color.BLACK; // 自定义属性:Tab按钮中文本的颜色
private int textSize = -1; // 自定义属性:Tab按钮的文本大小 private Bitmap tabBitmap; // 绘制元素的Bitmap
private Bitmap tmpBm; // 临时Bitmap
private Paint tabPaint; // 绘制元素的画笔
private Paint iconPaint; // 绘制图标的画笔
private Paint textPaint; // 绘制文本的画笔
private Bitmap iconBm; // 图标图片对应的Bitmap private float iconWidth; // 图标图片的宽度
private float iconHeight; // 图标图片的高度 private int alpha = 0; // 透明度 public GradientTab(Context context) {
this(context, null);
} public GradientTab(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
} public GradientTab(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 加载自定义属性
TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.GradientTab, defStyleAttr, 0);
int count = array.getIndexCount();
for (int i = 0; i < count; i++) {
int attr = array.getIndex(i);
switch (attr) {
case R.styleable.GradientTab_backColor:
backColor = array.getColor(attr, Color.GREEN);
break;
case R.styleable.GradientTab_iconImg:
int iconRes = array.getResourceId(attr, -1); // 自定义属性:Tab按钮中显示的图标的资源ID
if (iconRes != -1) {
iconBm = BitmapFactory.decodeResource(getResources(), iconRes);
iconWidth = iconBm.getWidth();
iconHeight = iconBm.getHeight();
}
break;
case R.styleable.GradientTab_spacing:
textSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, context.getResources().getDisplayMetrics())),
context.getResources().getDisplayMetrics());
break;
case R.styleable.GradientTab_text:
text.delete(0, text.length());
text.append(array.getString(attr));
break;
case R.styleable.GradientTab_textColor:
textColor = array.getColor(attr, Color.BLACK);
break;
case R.styleable.GradientTab_textSize:
textSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 14, context.getResources().getDisplayMetrics())),
context.getResources().getDisplayMetrics());
break;
}
}
array.recycle();
// 为一些自定义属性设置默认值
if (textSize == -1) {
textSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 14, context.getResources().getDisplayMetrics());
}
if (spacing == -1) {
spacing = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, context.getResources().getDisplayMetrics());
}
// 进行一些对象的初始化操作
init();
} /**
* 进行一些对象的初始化操作
*/
private void init() {
// 初始化绘制图标的画笔
iconPaint = new Paint();
iconPaint.setAntiAlias(true);
iconPaint.setDither(true);
// 初始化绘制文本的画笔
textPaint = new Paint();
textPaint.setAntiAlias(true);
textPaint.setDither(true);
textPaint.setColor(textColor);
textPaint.setTextSize(textSize);
// 初始化绘制元素的画笔
tabPaint = new Paint();
tabPaint.setAntiAlias(true);
tabPaint.setDither(true);
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = getMeasuredWidth();
height = getMeasuredHeight();
} @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// 初始化绘制元素的画布
tabBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas tabCanvas = new Canvas(tabBitmap);
// 对图标进行缩放
int widthLeft = width - getPaddingLeft() - getPaddingRight();
int heightLeft = height - getPaddingTop() - getPaddingBottom() - textSize - spacing;
float scale = (float) Math.min(widthLeft * 1.0 / iconWidth, heightLeft * 1.0 / iconHeight);
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
iconBm = Bitmap.createBitmap(iconBm, 0, 0, (int) iconWidth, (int) iconHeight, matrix, true);
iconWidth *= scale;
iconHeight *= scale;
// 将图标和文本绘制到绘制元素的画布上
tabCanvas.drawBitmap(iconBm, (widthLeft - iconWidth) / 2, getPaddingTop(), iconPaint);
int textWidth = (int) textPaint.measureText(text.toString());
tabCanvas.drawText(text.toString(), (widthLeft - textWidth) / 2, height - getPaddingBottom(), textPaint);
tmpBm = tabBitmap.copy(Bitmap.Config.ARGB_8888, false);
tabPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
tabPaint.setColor(backColor);
tabCanvas.drawRect(0, 0, width, height, tabPaint);
} @Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(tmpBm, 0, 0, null);
// 绘制图标和文本
Paint paint = new Paint();
paint.setAlpha(alpha);
canvas.drawBitmap(tabBitmap, 0, 0, paint);
} public void setAlpha(float alpha) {
this.alpha = (int) Math.ceil(255 * alpha);
invalidate();
} /**
* 当这个View所在的Activity临时结束时,保存当前状态(透明度)
*/
@Override
protected Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
bundle.putParcelable("default", super.onSaveInstanceState());
bundle.putInt("alpha", alpha);
return bundle;
} /**
* 当这个View所在的Activity重新打开时,重置当前状态(透明度)
*/
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
alpha = bundle.getInt("alpha");
super.onRestoreInstanceState(bundle.getParcelable("default"));
}
super.onRestoreInstanceState(state);
}
}
自定义属性文件 attr.xml 中的代码:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="backColor" format="color" />
<attr name="iconImg" format="reference" />
<attr name="spacing" format="dimension" />
<attr name="text" format="string" />
<attr name="textColor" format="color" />
<attr name="textSize" format="dimension" /> <declare-styleable name="GradientTab">
<attr name="backColor" />
<attr name="iconImg" />
<attr name="spacing" />
<attr name="text" />
<attr name="textColor" />
<attr name="textSize" />
</declare-styleable>
</resources>
主界面布局 activity_main.xml 文件中的代码:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"> <LinearLayout
android:id="@+id/gradienttab_main_ly_tabs"
android:layout_width="match_parent"
android:layout_height="60.0dip"
android:layout_alignParentBottom="true"
android:background="#EEEEEE"
android:orientation="horizontal"> <my.itgungnir.custom_gradientmenu.GradientTab
android:id="@+id/gradienttab_main_tab_tab1"
android:layout_width="0.0dip"
android:layout_height="match_parent"
android:layout_weight="1"
android:padding="3.0dip"
app:backColor="#008800"
app:iconImg="@mipmap/menu_tab1"
app:spacing="2.0dip"
app:text="Tab1"
app:textColor="#888888"
app:textSize="8.0sp" /> <my.itgungnir.custom_gradientmenu.GradientTab
android:id="@+id/gradienttab_main_tab_tab2"
android:layout_width="0.0dip"
android:layout_height="match_parent"
android:layout_weight="1"
android:padding="3.0dip"
app:backColor="#880000"
app:iconImg="@mipmap/menu_tab2"
app:spacing="2.0dip"
app:text="Tab2"
app:textColor="#888888"
app:textSize="8.0sp" /> <my.itgungnir.custom_gradientmenu.GradientTab
android:id="@+id/gradienttab_main_tab_tab3"
android:layout_width="0.0dip"
android:layout_height="match_parent"
android:layout_weight="1"
android:padding="3.0dip"
app:backColor="#000088"
app:iconImg="@mipmap/menu_tab3"
app:spacing="2.0dip"
app:text="Tab3"
app:textColor="#888888"
app:textSize="8.0sp" /> <my.itgungnir.custom_gradientmenu.GradientTab
android:id="@+id/gradienttab_main_tab_tab4"
android:layout_width="0.0dip"
android:layout_height="match_parent"
android:layout_weight="1"
android:padding="3.0dip"
app:backColor="#888888"
app:iconImg="@mipmap/menu_tab4"
app:spacing="2.0dip"
app:text="Tab4"
app:textColor="#888888"
app:textSize="8.0sp" />
</LinearLayout> <android.support.v4.view.ViewPager
android:id="@+id/gradienttab_main_vp_pages"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/gradienttab_main_ly_tabs" /> </RelativeLayout>
主界面JAVA文件 MainActivity.java 中的代码:
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager; import java.util.ArrayList;
import java.util.List; public class MainActivity extends FragmentActivity {
private ViewPager viewpager; private List<GradientTab> tabList; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
} @Override
protected void onResume() {
super.onResume();
viewpager = (ViewPager) findViewById(R.id.gradienttab_main_vp_pages);
initData();
initEvents();
} private void initData() {
// 初始化盛放Fragment的列表
final List<Fragment> list = new ArrayList<>();
for (int i = 1; i <= 4; i++) {
PageFragment fragment = new PageFragment();
Bundle bundle = new Bundle();
bundle.putString("title", "This is page " + i);
fragment.setArguments(bundle);
list.add(fragment);
}
// 为ViewPager适配数据
viewpager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
@Override
public Fragment getItem(int position) {
return list.get(position);
} @Override
public int getCount() {
return list.size();
}
});
// 初始化四个Tab按钮,添加到List列表中
tabList = new ArrayList<>();
tabList.add((GradientTab) findViewById(R.id.gradienttab_main_tab_tab1));
tabList.add((GradientTab) findViewById(R.id.gradienttab_main_tab_tab2));
tabList.add((GradientTab) findViewById(R.id.gradienttab_main_tab_tab3));
tabList.add((GradientTab) findViewById(R.id.gradienttab_main_tab_tab4));
} private void initEvents() {
// ViewPager的滚动事件
viewpager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
tabList.get(position).setAlpha(1 - positionOffset);
tabList.get((int) Math.ceil(position + positionOffset)).setAlpha(positionOffset == 0 ? 1 : positionOffset);
} @Override
public void onPageSelected(int position) {
} @Override
public void onPageScrollStateChanged(int state) {
}
});
}
}
运行效果图如下所示:
【Android - 自定义View】之自定义颜色渐变的Tab导航栏的更多相关文章
- Android 自定义View合集
自定义控件学习 https://github.com/GcsSloop/AndroidNote/tree/master/CustomView 小良自定义控件合集 https://github.com/ ...
- Android 自定义 view(三)—— onDraw 方法理解
前言: 上一篇已经介绍了用自己定义的属性怎么简单定义一个view<Android 自定义view(二) -- attr 使用>,那么接下来我们继续深究自定义view,下一步将要去简单理解自 ...
- Android 自定义 View 圆形进度条总结
Android 自定义圆形进度条总结 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 微信公众号:牙锅子 源码:CircleProgress 文中如有纰漏,欢迎大家留言指出. 最近 ...
- Android 自定义 View 详解
View 的绘制系列文章: Android View 绘制流程之 DecorView 与 ViewRootImpl Android View 的绘制流程之 Measure 过程详解 (一) Andro ...
- Android自定义View 画弧形,文字,并增加动画效果
一个简单的Android自定义View的demo,画弧形,文字,开启一个多线程更新ui界面,在子线程更新ui是不允许的,但是View提供了方法,让我们来了解下吧. 1.封装一个抽象的View类 B ...
- (转)[原] Android 自定义View 密码框 例子
遵从准则 暴露您view中所有影响可见外观的属性或者行为. 通过XML添加和设置样式 通过元素的属性来控制其外观和行为,支持和重要事件交流的事件监听器 详细步骤见:Android 自定义View步骤 ...
- Android 自定义View (五)——实践
前言: 前面已经介绍了<Android 自定义 view(四)-- onMeasure 方法理解>,那么这次我们就来小实践下吧 任务: 公司现有两个任务需要我完成 (1)监测液化天然气液压 ...
- Android 自定义 view(四)—— onMeasure 方法理解
前言: 前面我们已经学过<Android 自定义 view(三)-- onDraw 方法理解>,那么接下我们还需要继续去理解自定义view里面的onMeasure 方法 推荐文章: htt ...
- Android 自定义view(二) —— attr 使用
前言: attr 在前一篇文章<Android 自定义view -- attr理解>已经简单的进行了介绍和创建,那么这篇文章就来一步步说说attr的简单使用吧 自定义view简单实现步骤 ...
随机推荐
- 使用Magicodes.SwaggerUI快速配置SwaggerUI以及设置API分组
Magicodes.SwaggerUI 快速配置和集成SwaggerUI 特点 通过配置文件简单配置即可完成SwaggerUI的API格式JSON生成和集成 支持API分组和隐藏 支持自定义页面和验证 ...
- linux lsof 查看进程打开那些文件 或者 查看文件给那个进程使用
lsof命令是什么? 可以列出被进程所打开的文件的信息.被打开的文件可以是 1.普通的文件,2.目录 3.网络文件系统的文件,4.字符设备文件 5.(函数)共享库 6.管道,命名管道 7.符号链 ...
- C Primer Plus (一)
摘要:重读C Primer Plus ,查漏补缺 重读C Primer Plus,记录遗漏的.未掌握的.不清楚的知识点. 一.概览 1.链接器的作用是将这3个目标元素(目标代码.系统的标准启动代码和库 ...
- 实现ARM——Linux的自动登录
在使用Linux系统嵌入式开发时,往往需要设备绕过Linux的登录系统使其自动启动,比如我们常用的SSH客户端等.网上确实有很多方法,不知道是因为我们的ARM9板子是私人订制的缘故还是什么原因,试了很 ...
- NOIP模拟 32
我在31反思中膜拜过了B哥 没想到这次又... 我给老姚家丢脸了...STO 首先T1暴力就写挂了... 贪图从$n^3$*$2^n$优化成$n^2$*$2^n$然后打错了 哗哗的扔分 而且正解都想不 ...
- 用Vsftpd服务传输文件(铺垫篇)
文件传输协议 文件传输协议(FTP,File Transfer Protocol),即能够让用户在互联网中上传.下载文件的文件协议,而FTP服务器就是支持FTP传输协议的主机,要想完成文件传输则需要F ...
- Python基本数据结构之集合
一道python面试的一个小问题,说怎么使用一行代码将一个列表里的重复元素,其实这里只要将列表转换成集合就可以了. 定义 集合跟我们学的列表有点像,也是可以存一堆数据,不过它有几个独特的特点,令其在整 ...
- AutoCad 二次开发 .net 之层表的增加 删除 修改图层颜色 遍历 设置当前层
AutoCad 二次开发 .net 之层表的增加 删除 修改图层颜色 遍历 设置当前层 AutoCad 二次开发 .net 之层表的增加 删除 修改图层颜色 遍历 设置当前层我理解的图层的作用大概是把 ...
- NOIP模拟26
把题解沽了好久了,今天还是不想写,我们靠的B卷其实挺水的,但是我就是想吐槽一下!咋还带题目里面放题解的?题里一点题解的线索都没有,但是玄机竟然在题目里! 我也是醉了,T1就是一个贪心,题目说贪婪,T2 ...
- 由浅入深——从ArrayList浅谈并发容器
原创作品转载请附:https://www.cnblogs.com/superlsj/p/11655523.html 一.一个案例引发的思考 public class ArrayListTest { p ...