首先来介绍一下这个自定义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. 来玩一局CS吗?UE4射击游戏的独立服务器构建

    前言   根据UE4官方文档的介绍,UE4引擎在架构时就已经考虑到了多人游戏的情景,多人游戏基于客户端-服务器模式(CS模式).也就是说,会有一个服务器担当游戏状态的主控者,而连接的客户端将保持近似的 ...

  2. Pycharm 专业版激活码(转) 有效期到2020/06月

    亲测有效!!! 有效期截止为2020年06月,多谢大家支持与讨论! K6IXATEF43-eyJsaWNlbnNlSWQiOiJLNklYQVRFRjQzIiwibGljZW5zZWVOYW1lIjo ...

  3. egg-middleware 中间件

    Middleware 中间件 Egg 的中间件形式和 Koa 的中间件形式是一样的,都是基于洋葱圈模型.每次我们编写一个中间件,就相当于在洋葱外面包了一层. 编写中间件 写法 我们先来通过编写一个简单 ...

  4. Mui 长按保存图片

    必须先要 引入 mui.js,然后参考具体代码 <!DOCTYPE html> <html> <head> <meta charset="utf-8 ...

  5. Web for pentester_writeup之XSS篇

    Web for pentester_writeup之XSS篇 XSS(跨站脚本攻击) Example 1 反射性跨站脚本,URL中name字段直接在网页中显示,修改name字段, Payload: h ...

  6. C函数库stdio.h概况

    库变量 size_t    这是无符号整数类型,它是 sizeof 关键字的结果. FILE      这是一个适合存储文件流信息的对象类型. fpos_t   这是一个适合存储文件中任何位置的对象类 ...

  7. CSPS模拟测试59

    这场考得我心态爆炸......... 开场T1只会$n^{2}$,然后发现bfs时每个点只需要被更新一次,其他的更新都是没用的. 也就是说,我们可以只更新还没被更新的点? 于是我先YY了一个链表,发现 ...

  8. CSPS Oct目标

    超过skyh 删了一些sb话,不过目标不会变的

  9. 基于typedef的用法详解【转】

    也许新手用这个关键字不多,但它却是一个很有用的关键字,可以使代码模块化程度更好(即与其它代码的关联较少),在C++中还是实现Traits技术的基础,也是模板编程的基本语法之一. 若说变量定义是为变量命 ...

  10. mysql用find_in_set代替like搜索提高性能

    mysql用find_in_set代替like搜索提高性能 <pre>SELECT * from mobantestinfo1 where find_in_set('33',info2); ...