首先来介绍一下这个自定义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导航栏的更多相关文章

  1. Android 自定义View合集

    自定义控件学习 https://github.com/GcsSloop/AndroidNote/tree/master/CustomView 小良自定义控件合集 https://github.com/ ...

  2. Android 自定义 view(三)—— onDraw 方法理解

    前言: 上一篇已经介绍了用自己定义的属性怎么简单定义一个view<Android 自定义view(二) -- attr 使用>,那么接下来我们继续深究自定义view,下一步将要去简单理解自 ...

  3. Android 自定义 View 圆形进度条总结

    Android 自定义圆形进度条总结 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 微信公众号:牙锅子 源码:CircleProgress 文中如有纰漏,欢迎大家留言指出. 最近 ...

  4. Android 自定义 View 详解

    View 的绘制系列文章: Android View 绘制流程之 DecorView 与 ViewRootImpl Android View 的绘制流程之 Measure 过程详解 (一) Andro ...

  5. Android自定义View 画弧形,文字,并增加动画效果

    一个简单的Android自定义View的demo,画弧形,文字,开启一个多线程更新ui界面,在子线程更新ui是不允许的,但是View提供了方法,让我们来了解下吧. 1.封装一个抽象的View类   B ...

  6. (转)[原] Android 自定义View 密码框 例子

    遵从准则 暴露您view中所有影响可见外观的属性或者行为. 通过XML添加和设置样式 通过元素的属性来控制其外观和行为,支持和重要事件交流的事件监听器 详细步骤见:Android 自定义View步骤 ...

  7. Android 自定义View (五)——实践

    前言: 前面已经介绍了<Android 自定义 view(四)-- onMeasure 方法理解>,那么这次我们就来小实践下吧 任务: 公司现有两个任务需要我完成 (1)监测液化天然气液压 ...

  8. Android 自定义 view(四)—— onMeasure 方法理解

    前言: 前面我们已经学过<Android 自定义 view(三)-- onDraw 方法理解>,那么接下我们还需要继续去理解自定义view里面的onMeasure 方法 推荐文章: htt ...

  9. Android 自定义view(二) —— attr 使用

    前言: attr 在前一篇文章<Android 自定义view -- attr理解>已经简单的进行了介绍和创建,那么这篇文章就来一步步说说attr的简单使用吧 自定义view简单实现步骤 ...

随机推荐

  1. CVE-2019-13272 Linux kernel 权限许可和访问控制问题漏洞

    漏洞简介: Linuxkernel是美国Linux基金会发布的开源操作系统Linux所使用的内核. Linuxkernel5.1.17之前版本中存在安全漏洞,该漏洞源于kernel/ptrace.c文 ...

  2. MIT线性代数:3.矩阵相乘

  3. 学习笔记22_AspMvc简介

    *Mvc和webForm区别 1. Mvc模式下,前台和后台的交流,是后台提供数据,使用对象包裹的形式,前台来使用,类似于webForm定义一个属性那样. 2.Mvc模式下,再也不是使用this.la ...

  4. AD中如何插入logo(图片)

    图片转成protel altium AD PCB封装 LOGO方法 1. 2. 3. 4.打开下列顺序文件夹 Examples-->Scripts-->Delphiscript Scrip ...

  5. [Python]python面向对象 __new__方法及单例设计

    __new__ 方法 使用 类名() 创建对象时,Python 的解释器 首先 会 调用 __new__ 方法为对象 分配空间 __new__ 是一个 由 object 基类提供的 内置的静态方法,主 ...

  6. hadoop2.6集群环境搭建

    版权声明:本文为博主原创文章,未经博主允许不得转载. 一.环境说明 1.机器:一台物理机 和一台虚拟机 2.Linux版本:[Spark@S1PA11 ~]$ cat /etc/issueRed Ha ...

  7. svg路径蒙版动画

    svg路径蒙版动画,是比较实用的一种动画效果,能够绘制如下图所示的动画. 接下来细说这样的动画是如何做成的: 1.准备工作 2.SVG路径动画 3.SVG路径蒙版动画 4.复杂图形的编辑技巧 1.准备 ...

  8. 爬虫之request库主要解析---参照慕课北理工嵩天

    kv = {'key1':'value1','key2':'value2'} r = requests.request (' GET' , 'http://python123.io/ws' , par ...

  9. [ASP.NET Core 3框架揭秘] 文件系统[1]:抽象的“文件系统”

    ASP.NET Core应用 具有很多读取文件的场景,比如配置文件.静态Web资源文件(比如CSS.JavaScript和图片文件等)以及MVC应用的View文件,甚至是直接编译到程序集中的内嵌资源文 ...

  10. 2019年PHP最新面试题(含答案)

    1. 数据库设计经验,为什么进行分表?分库?一般多少数据量开始分表?分库?分库分表的目的?什么是数据库垂直拆分?水平拆分?分区等等 一:为什么要分表 当一张表的数据达到几百万时,你查询一次所花的时间会 ...