先来看看最初版代码:
public class GradualChangeTv extends AppCompatTextView {
public Paint mPaint = new Paint();
}
就是简单的绘制了一行字。
疑问
为什么这里要继承自AppCompatTextView而不是View?
答:偷个懒而已,因为不用在我来测量View,直接用父类的就行
来看看效果顺便也看看布局:
图片
出现问题
文字并没有显示。
答:因为文字坐标系和屏幕坐标系不一样,文字坐标系是从BaseLine线开始计算的。
先来回顾一下屏幕的坐标系:
图片
再来看看文字的坐标系。
图片
再来思考一下文字是为什么不显示的:
图片
虚线为BaseLine
如果此时我把字体放大到100,看一看我能不能看到文字。
图片
再一次证明了文字是从BaseLine线开始绘制。
文字居中
可以用两条辅助线,水平线与垂直线。然后在来看文字是否居中。
代码
⚠️ 底部会给出完整代码。这里看思路即可,不用复制代码。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//获取当前控件的宽高
int viewWidth = getWidth() / 2;
int viewHeight = getHeight() / 2;
/*
* 绘制文字
* 参数一: 绘制文字
* 参数二: x轴开始位置
* 参数三: y 轴开始位置
* 参数四: 画笔
*/
canvas.drawText(text, viewWidth, viewHeight, mPaint);
效果图
图片
可以看出,还是上面说的那个问题,文字绘制是基于baseLine线来绘制的。
文字居中思路:
通过mPaint.measureText(text) 获取文字宽
通过mPaint.descent() + mPaint.ascent(); 获取文字高
然后控件各取一半,让控件减去即可
这里的descent和ascent可以参考上面文字绘制图。
相关代码
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//文字宽度
float textWidth = mPaint.measureText(text);
//文字高度
float textHeight = mPaint.descent() + mPaint.ascent();
效果图
图片
裁剪
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//文字宽度
float textWidth = mPaint.measureText(text);
//文字高度
float textHeight = mPaint.descent() + mPaint.ascent();
裁剪(clipRect)参数分析:
参数一: 从文字开始位置绘制
参数二: 顶部裁剪为0
参数三: 裁剪宽度
参数四: 绘制高度
canvas的save()和restore()方法可以理解为将当前绘制的东西当作一个新的图层!
来看看效果图:
图片
代码注释很清晰,就不过多解释了。
从左到右渐变文字
众所周知,在android中是不能够将文字绘制一般的。
思路分析:
绘制两层(两层颜色不同),两层叠加起来
然后通过裁剪将上面一层给裁剪掉
图片
在来看看现在代码是什么样子的:
//用来记录当前进度 【0-1】
float progress = 0.3f;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//文字宽度
float textWidth = mPaint.measureText(text);
//文字高度
float textHeight = mPaint.descent() + mPaint.ascent();
这里重点解释一下上层[需要裁剪的]参数:
//裁剪
canvas.clipRect((int) left, 0, (int) left + textWidth * progress, getHeight());
textWidth需要绘制文字的宽度
viewWidth控件宽度的一半
文字开始的位置:left = viewWidth - textWidth / 2
文字需要裁剪的位置:文字的宽度 * progress
图片
通过手势滑动来控制。这段代码并没有实质性作用,只是来看看效果。
@SuppressLint(“ClickableViewAccessibility”)
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_MOVE) {
progress = event.getX() / getWidth();
invalidate();
}
return true;
}
效果图
图片
从右到左渐变文字
思路和从左到右绘制是一样的直接看关键代码:
private void drawRightToLeft(Canvas canvas, int viewWidth, int viewHeight, float textWidth, float textHeight) {
mPaint.setColor(Color.GREEN);
/*
* 这里 left和right能够在此抽取出来,不过这样写很易懂,有需求自己弄吧!!!
*/
canvas.save();
//绘制文字X轴的位置 【文字开始的位置】
float textX = viewWidth - textWidth / 2;
@SuppressLint(“ClickableViewAccessibility”)
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_MOVE) {
if (type == GradualChangeTextView.GRADUAL_CHANGE_RIGHT) {
//从右到左滑动
progress = 1 - event.getX() / getWidth();
} else if (type == GradualChangeTextView.GRADUAL_CHANGE_LEFT) {
//从左到右滑动
progress = event.getX() / getWidth();
}
invalidate();
}
return true;
}
效果图
图片
最后在添加两个按钮来完全测试一下代码有没有问题。
图片
完完全全没有问题!
最终实现效果(渐变滑动)
先来看看布局:
图片
布局简单的很,就是文字和ViewPager。大致看看ViewPager代码:
//text1 … text4 是控件id
val textList = listOf(text1, text2, text3, text4)
这段代码只要学过就懂,不细说了。重中之重来了:
viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int,
) {
if (positionOffset > 0) {
val left = textList[position]
val right = textList[position + 1]
//从右到左滑动
left.setSlidingPosition(GradualChangeTextView.GRADUAL_CHANGE_RIGHT)
//从左到右滑动
right.setSlidingPosition(GradualChangeTextView.GRADUAL_CHANGE_LEFT)
来看看效果
图片
过度绘制极限优化
什么是过度绘制,参考文档:
https://www.jianshu.com/p/2cc6d5842986
重点总结
原色 – 没有被过度绘制 – 这部分的像素点只在屏幕上绘制了一次。
蓝色 – 1次过度绘制– 这部分的像素点只在屏幕上绘制了两次。
绿色 – 2次过度绘制 – 这部分的像素点只在屏幕上绘制了三次。
粉色 – 3次过度绘制 – 这部分的像素点只在屏幕上绘制了四次。
红色 – 4次过度绘制 – 这部分的像素点只在屏幕上绘制了五次。
先来看看没有优化的效果:
图片
可以看到,在绘制的过程中,因为是两层,那么就绘制了2次。
优化思路
当黑色[上层]从左到右滑动的时候,红色[下层]跟随着从左到右裁剪。来看看下层绘制的代码:
//绘制下层 不动的
private void drawBottom(Canvas canvas, int viewWidth, int viewHeight, float textWidth, float textHeight) {
mPaint.setColor(Color.RED);
canvas.save();
//绘制文字X轴的位置 [文字开始的位置]
float textX = viewWidth - textWidth / 2;