上次看了鸿洋的博客,其中有一篇博客实现的是字体逐渐变色的效果,不过其使用的是clipRect实现的,今天给大家带来另一种实现方式,通过XferMode来实现。先看效果吧。
先说下实现原理:我们首先绘制当前显示的文本内容,然后设置xfermode的值为PorterDuff.Mode.SRC_IN,然后子啊绘制一个和我们当前view高度相同,并且宽度不断增大的绿色矩形。废话不多说,下面看具体实现:
自定义属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="text_value" format="string"/>
<attr name="text_size" format="dimension"/>
<declare-styleable name="filter_text">
<attr name="text_value" />
<attr name="text_size" />
</declare-styleable>
</resources>
这里我只定义了两个属性:
- text_value 表示需要绘制的文本内容
- text_size 表示绘制的文本字体的大小
获取自定义属性并且初始化
在构造方法中,获取自定义的属性,并且进行必要的初始化工作。
public FilterText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//获取自定义属性的值
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.filter_text);
mTextValue = array.getString(R.styleable.filter_text_text_value);
mTextSize = array.getDimensionPixelSize(R.styleable.filter_text_text_size, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
//TypedArray用完记得回收
array.recycle();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
//设置文本的字体大小
mPaint.setTextSize(mTextSize);
mBound = new Rect();
//用来计算当前mTextValue的宽度和高度
mPaint.getTextBounds(mTextValue, 0, mTextValue.length(),mBound);
}
这里需要注意,由于我们等下对于该自定义view的宽高会设置为wrap_content,所以需要重写onMeasure方法来测量我们当前的view的宽高,所以需要在测量之前设置好字体的大小,否则会影响测量值。
重写onMeasure方法
/**
* 测量当前view的大小
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int finalWidth = 0;
int finalHeight = 0;
if (widthMode == MeasureSpec.EXACTLY) {
//如果测量模式是EXACTLY类型,则直接使用推荐值加上padding的值
finalWidth = widthSize + getPaddingLeft() + getPaddingRight();
} else {
finalWidth = getPaddingLeft() + getPaddingRight() + mBound.width();
if (widthMode == MeasureSpec.AT_MOST) {
//如果测量模式是AT_MOST,则取期望值和我们计算出的最小值
finalWidth = Math.min(finalWidth, widthSize);
}
}
if (heightMode == MeasureSpec.EXACTLY) {
finalHeight = heightSize + getPaddingBottom() + getPaddingTop();
} else {
finalHeight = getPaddingTop() + getPaddingBottom() + mBound.height();
if (heightMode == MeasureSpec.AT_MOST) {
finalHeight = Math.min(finalHeight, heightSize);
}
}
//记得调用setMeasuredDimension方法
setMeasuredDimension(finalWidth, finalHeight);
}
这里首先获得宽高的测量模式和推荐的值,然后根据具体的测量模式来计算当前view的宽度和高度。最后记得调用setMeasuredDimension方法。
重写onDraw方法
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
Paint paint = new Paint();
//创建一个图层,在图层上演示图形混合后的效果
int sc = canvas.saveLayer(0, 0, width, height, null, Canvas.MATRIX_SAVE_FLAG |
Canvas.CLIP_SAVE_FLAG |
Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
Canvas.FULL_COLOR_LAYER_SAVE_FLAG |
Canvas.CLIP_TO_LAYER_SAVE_FLAG);
//先绘制“dest
canvas.drawBitmap(getDesBitmap(width,height), 0 , 0 , paint);
//设置Paint的Xfermode
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
mRectWidth += 10;
canvas.drawBitmap(getSrcBitmap(mRectWidth,height), 0 , 0, paint);
//重置当前Xfermode
paint.setXfermode(null);
// 还原画布
canvas.restoreToCount(sc);
if (mRectWidth < width) {//不断重绘当前view
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
invalidate();
}
}
主要分为以下几步:
- 首先获得当前view的宽度和高度
int width = getWidth();
int height = getHeight();
- 创建一个图层,在图层上演示图形混合后的效果,注意一定要创建该图层,否则会出现两个图形交集之外部分为黑色
int sc = canvas.saveLayer(0, 0, width, height, null, Canvas.MATRIX_SAVE_FLAG |
Canvas.CLIP_SAVE_FLAG |
Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
Canvas.FULL_COLOR_LAYER_SAVE_FLAG |
Canvas.CLIP_TO_LAYER_SAVE_FLAG);
- 绘制src和dst
//先绘制src
canvas.drawBitmap(getSrcBitmap(width,height), 0 , 0 , paint);
//设置Paint的Xfermode
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
//每次绘制之前将dst的宽度加上10个像素,高度保持和src相同
mRectWidth += 10;
canvas.drawBitmap(getDesBitmap(mRectWidth,height), 0 , 0, paint);
//重置当前Xfermode
paint.setXfermode(null);
// 还原画布
canvas.restoreToCount(sc);
这里的getSrcBitmap和getDesBitmap就是我们分别绘制的文本和过滤的背景色矩形,如下:
// 创建一个矩形bitmap,作为src的bitmap
private Bitmap getSrcBitmap(int w, int h) {
Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bm);
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
//绘制边框
p.setStrokeWidth(3);
p.setColor(Color.RED);
p.setStyle(Paint.Style.STROKE);
c.drawRect(0, 0, w, h, p);
//绘制文字
p.setColor(Color.BLACK);
p.setStyle(Paint.Style.FILL);
p.setTextSize(mTextSize);
c.drawText(mTextValue,w / 2 - mBound.width() / 2, h / 2 + mBound.height() / 2, p);
return bm;
}
// 创建一个矩形bitmap,作为dst的bitmap
private Bitmap getDesBitmap(int w, int h) {
//首先创建一个位图
Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
//创建一个和当前位图相同大小的canvas对象
Canvas c = new Canvas(bm);
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setColor(Color.GREEN);
c.drawRect(0,0,w,h ,p);
return bm;
}
总结以下:
- 测量当前view的宽和高,如果有字体大小需要在onMeasure测量之前就设置其大小,否则影响测量值
- 在onDraw方法中具体的绘制工作:
- 创建一个图层,在图层上演示图形混合后的效果
- 先绘制src
- 设置Paint的Xfermode,PorterDuff.Mode.SRC_IN
- 绘制dst
- 重置当前Xfermode
还原画布
ok,今天就到这里了,希望大家喜欢。