前言
今日头条上面的渐变tab很酷炫,也很简约。因为在项目中也经常会用到很多viewpager+fragment(很丑陋)。所以就很好奇头条上具体是怎么实现的。经过搜索发现已经有人给出实现方案http://blog.csdn.net/tyyj90/article/details/45288431。看了这篇文章,自己一个一个代码敲了一个demo(写代码跟做数学题一个样,一定要自己亲手做一遍才能真正了解里面的原理。而不能只是看看而已。。。)。之所以要写这篇文章主要是想练练自己最基本的书写能力和表达能力,再则也是想总结从里面得到的一些经验。(因为这demo撑死算个半原创)。 大概效果如下图:
实现和原理分析
总的实现很简单:viewpager+自定义控件
viewpager大家应该都很经常用,所以在这里不多介绍。这边主要讲讲自定义view。自定义控件是android程序员近阶段的必经之路。自定义view的步骤如下:
1、自定义View的属性
2、在View的构造方法中获得我们自定义的属性
3、重写onMesure (可以不需要)
4、重写onDraw
1、自定义view属性
首先在res/values/ 下建立一个attrs.xml , 在里面定义我们的属性和声明我们的整个样式。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="titleText" format="string"/>
<attr name="titleTextColor1" format="color"/>
<attr name="titleTextSize" format="dimension"/>
<attr name="changeColor" format="color"/>
<attr name="originColor" format="color"/>
<declare-styleable name="TabView">
<attr name="titleText"/>
<attr name="titleTextColor1"/>
<attr name="titleTextSize"/>
<attr name="changeColor"/>
<attr name="originColor"/>
</declare-styleable>
</resources>
我们定义了标题名称,标题颜色,字体大小,原始颜色,变化颜色5个属性,format是值该属性的取值类型:一共有:string,color,demension,integer,enum,reference,float,boolean,fraction,flag;
然后在布局中声明我们的自定义View
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tab="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:orientation="vertical"
android:paddingTop="@dimen/activity_vertical_margin">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="30dp"
android:orientation="horizontal">
<com.tabgradient.tabView
android:id="@+id/tab1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
tab:titleText="tab1"
tab:titleTextSize="25sp"
tab:titleTextColor1="@android:color/black"
tab:changeColor="@android:color/holo_red_dark"
tab:originColor="@android:color/darker_gray"/>
<com.tabgradient.tabView
android:id="@+id/tab2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
tab:titleText="tab2"
tab:titleTextSize="25sp"
tab:titleTextColor1="@android:color/black"
tab:changeColor="@android:color/holo_red_dark"
tab:originColor="@android:color/darker_gray"/>
<com.tabgradient.tabView
android:id="@+id/tab3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
tab:titleText="tab3"
tab:titleTextSize="25sp"
tab:titleTextColor1="@android:color/black"
tab:changeColor="@android:color/holo_red_dark"
tab:originColor="@android:color/darker_gray"/>
<com.tabgradient.tabView
android:id="@+id/tab4"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
tab:titleText="tab4"
tab:titleTextSize="25sp"
tab:titleTextColor1="@android:color/black"
tab:changeColor="@android:color/holo_red_dark"
tab:originColor="@android:color/darker_gray"/>
</LinearLayout>
<android.support.v4.view.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</android.support.v4.view.ViewPager>
</LinearLayout>
一定要引入: xmlns: tab = "http://schemas.android.com/apk/res-auto"
2、在View的构造方法中,获得我们的自定义的样式
/**
* * 文本
*/
private String mText;
/**
* 文本的颜色
*/
private int mTextColor;
/**
* 文本的大小
*/
private int mTextSize;
/**
* 原始颜色
*/
private int mOriginColor;
/**
* 变化颜色
*/
private int mChangeColor;
private Paint mPaint;
Rect mTextBound = new Rect();
private int mRealWidth;
private int mTextStartX;
private int mTextWidth;
private static final int DIRECTION_LEFT = 0;
private static final int DIRECTION_RIGHT = 1;
//以下定义一些默认的变量
private int mDirection = DIRECTION_LEFT;
private float mProgress; // 进度,从0到1之间取值
public tabView(Context context) {
super(context);
}
public tabView(Context context, AttributeSet attrs) {
super(context, attrs);
/**
* 获得我们所定义的自定义样式属性
*/
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabView);
int n = a.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.TabView_titleText:
mText = a.getString(attr);
break;
case R.styleable.TabView_titleTextColor1:
mTextColor = a.getColor(attr, mTextColor);
break;
case R.styleable.TabView_titleTextSize:
mTextSize = a.getDimensionPixelSize(attr, mTextSize);
break;
case R.styleable.TabView_changeColor:
mChangeColor = a.getColor(attr, mChangeColor);
break;
case R.styleable.TabView_originColor:
mOriginColor = a.getColor(attr, mOriginColor);
break;
}
}
a.recycle();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setTextSize(mTextSize);
}
3、重写onMesure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasuredWidth(widthMeasureSpec);
int height = MeasuredHeight(heightMeasureSpec);
setMeasuredDimension(width, height);
mTextWidth = (int) mPaint.measureText(mText);
//这个函数可以获取最小的包裹文字矩形,赋值到mTextBound
mPaint.getTextBounds(mText, 0,mText.length(), mTextBound);
mRealWidth=getMeasuredWidth()-getPaddingRight()-getPaddingLeft();
mTextStartX=mRealWidth/2-mTextWidth/2;
}
private int MeasuredHeight(int heightMeasureSpec) {
int mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
int result = 0;
switch (mode) {
case MeasureSpec.EXACTLY:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.UNSPECIFIED:
result=mTextBound.height();
break;
}
result =mode==MeasureSpec.AT_MOST ? Math.min(size,result) : result;
return result+getPaddingBottom()+getPaddingTop();
}
private int MeasuredWidth(int widthMeasureSpec) {
int mode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
int result = 0;
switch (mode) {
case MeasureSpec.EXACTLY:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.UNSPECIFIED:
result=mTextBound.width();
break;
}
result =mode==MeasureSpec.AT_MOST ? Math.min(size,result) : result;
return result+getPaddingLeft()+getPaddingRight();
}
根据传入值widthMeasureSpec、heightMeasureSpec获取模式和值,如果模式是EXACTLY直接返回获取的值,如果是AT_MOST、UNSPECIFIED那么就进行自己测量需要的空间,注意如果是AT_MOST不应该大于父类传入值
4、重写onDraw
@Override
protected void onDraw(Canvas canvas) {
if (mDirection==DIRECTION_LEFT)
{
drawChangeLeft(canvas);
drawOriginLeft(canvas);
}
else if (mDirection==DIRECTION_RIGHT)
{
drawChangeRight(canvas);
drawOriginRight(canvas);
}
}
private void drawOriginRight(Canvas canvas) {
drawText(canvas,mOriginColor,mTextStartX,(int)(mTextStartX+mTextWidth*(1-mProgress)));
}
private void drawChangeRight(Canvas canvas) {
drawText(canvas,mChangeColor,(int)(mTextStartX+mTextWidth*(1-mProgress)),mTextStartX+mTextWidth);
}
private void drawOriginLeft(Canvas canvas) {
drawText(canvas,mOriginColor,(int)(mTextStartX+mTextWidth*mProgress),mTextStartX+mTextWidth);
}
private void drawChangeLeft(Canvas canvas) {
drawText(canvas,mChangeColor,mTextStartX,(int)(mTextStartX+mTextWidth*mProgress));
}
private void drawText(Canvas canvas, int color, int startX, int endX) {
mPaint.setColor(color);
canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.clipRect(startX, 0, endX, getMeasuredHeight());
canvas.drawText((String) mText, mTextStartX, getMeasuredHeight()
/ 2 + mTextBound.height() / 2, mPaint);
canvas.restore();
mPaint.setColor(color);
canvas.save(Canvas.ALL_SAVE_FLAG);
}
public float getProgress() {
return mProgress;
}
public void setProgress(float mProgress) {
this.mProgress = mProgress;
invalidate();//改变进度,需要重绘,实现字体渐变的效果
}
public int getDirection() {
return mDirection;
}
public void setDirection(int mDirection) {
this.mDirection = mDirection;
invalidate(); //重新改变方向,需要重绘
}
绘制的核心就在于利用mProgress和方向去计算clip的范围
该源码地址:https://github.com/handsometong/tabgradient