需求来源
![](https://img.haomeiwen.com/i7559548/0d91cc86a47928f8.png)
上面的滑动字体变色实现是怎么做的的呢?
实现思路
1.继承自View还是TextView?
由于继承自TextView我们不需要测量宽高,少做很多处理。所以我们选择TextView
2.实现两种颜色的TextView
2.1. TextView两种颜色,红色和黑色,根据当前进度显示左右不同的颜色
2.2. 在onDraw()中利用计算好的位置分割然后绘制文字,裁剪绘制部分即可。
2.3. 使用canvas.drawText(text, x, y, paint);绘制文字
具体实现
先获取自定义属性:
private void initPaint(Context context, AttributeSet attrs) {
//获取自定义属性
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ColorTrackTextView);
//获取默认色
int originColor = array.getColor(R.styleable.ColorTrackTextView_originColor, getTextColors().getDefaultColor());
//获取变化的颜色
int changeColor = array.getColor(R.styleable.ColorTrackTextView_changeColor, getTextColors().getDefaultColor());
mOriginPaint = getPaintByColor(originColor);
mChangePaint = getPaintByColor(changeColor);
//记得回收,为什么要回收?稍后做解释
array.recycle();
}
绘制不同颜色
@Override
protected void onDraw(Canvas canvas) {
//获取变色进度
int middle = (int) (mCurrentProgress * getWidth());
if (mDirection == Direction.LEFT_TO_RIGHT) {//左边是红色右边是黑色
//绘制变色,从0-middle为变化色,后面为默认色
drawText(canvas, mChangePaint, 0, middle);
drawText(canvas, mOriginPaint, middle, getWidth());
} else {
drawText(canvas, mChangePaint, getWidth() - middle, getWidth());
drawText(canvas, mOriginPaint, 0, getWidth() - middle);
}
}
private void drawText(Canvas canvas, Paint paint, int start, int end) {
//canvas的save方法作用后面做解释
canvas.save();
//剪辑矩形,截取绘制的内容
Rect rect = new Rect(start, 0, end, getHeight());
canvas.clipRect(rect);
String text = getText().toString();
Rect bounds = new Rect();
//获取文字的范围
paint.getTextBounds(text, 0, text.length(), bounds);
//获取字体的宽度
int x = getWidth() / 2 - bounds.width() / 2;
Paint.FontMetricsInt fontMetricsInt = paint.getFontMetricsInt();
//文字高度的一半到基线的距离
//top表示基线到文字最上面的位置的距离 是个负值 bottom为基线到最下面的距离,为正值
int dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
//基线
int baseLine = getHeight() / 2 + dy;
//这里drawText中的x和y是原点坐标,什么是原点坐标看图
canvas.drawText(text, x, baseLine, paint);
canvas.restore();
}
![](https://img.haomeiwen.com/i7559548/f5b105ca06e9fe57.jpg)
array.recycle()
TypedArray是获取XML layout的属性值,使用完后要使用recycle()方法将TypedArray回收。TypedArray并没有占用IO,线程,仅仅是一个变量为什么要回收呢?
点击obtainStyledAttributes一直走找到ResourcesImpl的obtainStyledAttributes的这行代码
final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
看到实现是TypedArray的静态方法
static TypedArray obtain(Resources res, int len) {
//从ArrayPool中获取的
TypedArray attrs = res.mTypedArrayPool.acquire();
if (attrs == null) {
attrs = new TypedArray(res);
}
......
}
程序在运行时维护了一个 TypedArray的池,程序调用时,会向该池中请求一个实例,用完之后,调用 recycle() 方法来释放该实例,从而使其可被其他模块复用。
那为什么要使用这种使用池+单例的模式?由于array会随着Activity创建而创建,因此,需要系统频繁的创建array,对内存和性能是一个不小的开销,如果不使用池模式,每次都让GC来回收,很可能就会造成OutOfMemory。
canvas.save()和canvas.restore()
sava()方法:
将当前的matrix和clip保存到一个私有堆栈中。后续调用translate,scale,rotate,skew,concat或clipRect clipPath都将照常,但调用restore(),这些将被重置,save()之前设置将被恢复。
restore()方法:
这个调用平衡了之前的save()调用,并用于删除自上次保存调用以来对matrix /clip状态的所有修改。调用restore()不能比调用save()次数更多
我个人理解的是相当于打个标记,类似于还原点。
自定义View源码
到此为止我们就可以看到这样的效果
![](https://img.haomeiwen.com/i7559548/85dc52a6b33cd2a3.gif)
注意:
onDraw中尽量不要new对象,因为要频繁调用。
网友评论