Android字体渐变效果

news/2024/11/29 20:48:22/

先来看看最初版代码:

public class GradualChangeTv extends AppCompatTextView {
public Paint mPaint = new Paint();

public final String text = "android 超级兵";public GradualChangeTv(Context context) {this(context, null);
}public GradualChangeTv(Context context, AttributeSet attrs) {this(context, attrs, 0);
}public GradualChangeTv(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);mPaint.setColor(Color.RED);//抗锯齿mPaint.setAntiAlias(true);
}@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);/** 绘制文字* 参数一: 绘制文字* 参数二: x轴开始位置* 参数三: y 轴开始位置* 参数四: 画笔*/canvas.drawText(text, 0, 0, mPaint);
}

}

就是简单的绘制了一行字。

疑问

为什么这里要继承自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);

    //绘制居中线drawCenterLine(canvas, viewWidth, viewHeight);
}private void drawCenterLine(Canvas canvas, int viewWidth, int viewHeight) {//垂直线canvas.drawLine(viewWidth, 0, viewWidth, getHeight(), mPaint);//水平线canvas.drawLine(0,viewHeight,getWidth(),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();

    //获取当前控件的宽高int viewWidth = getWidth() / 2;int viewHeight = getHeight() / 2;canvas.drawText(text, viewWidth - textWidth / 2, viewHeight - textHeight / 2, mPaint);//绘制居中线drawCenterLine(canvas, viewWidth, viewHeight);
}

效果图

图片

裁剪

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//文字宽度
float textWidth = mPaint.measureText(text);
//文字高度
float textHeight = mPaint.descent() + mPaint.ascent();

    //获取当前控件的宽高的一半int viewWidth = getWidth() / 2;int viewHeight = getHeight() / 2;//裁剪drawClip(canvas, viewWidth, viewHeight, textWidth, textHeight);//绘制居中线drawCenterLine(canvas, viewWidth, viewHeight);
}private void drawClip(Canvas canvas, int viewWidth, int viewHeight, float textWidth, float textHeight) {mPaint.setColor(Color.BLACK);canvas.save();//绘制文字X轴的位置float left = viewWidth - textWidth / 2;//绘制文字Y轴的位置float right = viewHeight - textHeight / 2;//裁剪canvas.clipRect((int) left, 0, (int) left + 300, getHeight());/** 绘制文字* 参数一: 绘制文字* 参数二: x轴开始位置* 参数三: y 轴开始位置* 参数四: 画笔*/canvas.drawText(text, left, right, mPaint);canvas.restore();
}

裁剪(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();

    //获取当前控件的宽高的一半int viewWidth = getWidth() / 2;int viewHeight = getHeight() / 2;//绘制底层drawBottom(canvas, viewWidth, viewHeight, textWidth, textHeight);//绘制上层【颜色渐变的】drawUp(canvas, viewWidth, viewHeight, textWidth, textHeight);//绘制居中线drawCenterLine(canvas, viewWidth, viewHeight);
}//绘制上层【渐变的】
private void drawUp(Canvas canvas, int viewWidth, int viewHeight, float textWidth, float textHeight) {mPaint.setColor(Color.BLACK);canvas.save();//绘制文字X轴的位置float left = viewWidth - textWidth / 2;//绘制文字Y轴的位置float right = viewHeight - textHeight / 2;//裁剪canvas.clipRect((int) left, 0, (int) left + textWidth * progress, getHeight());/** 绘制文字* 参数一: 绘制文字* 参数二: x轴开始位置* 参数三: y 轴开始位置* 参数四: 画笔*/canvas.drawText(text, left, right, mPaint);canvas.restore();
}//绘制下层 不动的
private void drawBottom(Canvas canvas, int viewWidth, int viewHeight, float textWidth, float textHeight) {mPaint.setColor(Color.RED);  //文字颜色canvas.save();//文字开始位置float left = viewWidth - textWidth / 2;/** 绘制文字* 参数一: 绘制文字* 参数二: x轴开始位置* 参数三: y 轴开始位置* 参数四: 画笔*/canvas.drawText(text, left, viewHeight - textHeight / 2, mPaint);canvas.restore();
}

这里重点解释一下上层[需要裁剪的]参数:

//裁剪
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;

    //绘制文字Y轴的位置float textY = viewHeight - textHeight / 2;//文字结束的位置float end = viewWidth + mPaint.measureText(text) / 2;canvas.clipRect(end, 0, textX + textWidth * (1 - progress), getHeight());canvas.drawText(text, textX, textY, mPaint);canvas.restore();
}

@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)

    val list = listOf(HomeFragment(), MyFragment(), TestFragment(), SettingFragment())val viewPagerAdapter = ViewPagerAdapter(supportFragmentManager, list)viewPager.adapter = viewPagerAdapter//默认选择第一页viewPager.currentItem = 1//默认选中textList[viewPager.currentItem].percent = 1f

这段代码只要学过就懂,不细说了。重中之重来了:

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)

                //当前页面取反[从右到左]left.percent = 1 - positionOffset//下一个页面正常[从左到右]right.percent = positionOffset}}override fun onPageSelected(position: Int) { }override fun onPageScrollStateChanged(state: Int) {//当 ViewPage结束的时候,重新设置一下状态 [不设置的话滑动太快,会导致'残影']textList.forEach {if (it.tag == textList[viewPager.currentItem].tag) {it.percent = 1f} else {it.percent = 0f}}}})

来看看效果

图片

过度绘制极限优化

什么是过度绘制,参考文档:
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;

    //绘制文字Y轴的位置float textY = viewHeight - textHeight / 2;//跟随者上层裁剪canvas.clipRect((int) textX + textWidth * progress, 0, textWidth + viewWidth, getHeight());/** 绘制文字* 参数一: 绘制文字* 参数二: x轴开始位置* 参数三: y 轴开始位置* 参数四: 画笔*/canvas.drawText(text, textX, textY, mPaint);canvas.restore();
}

http://www.ppmy.cn/news/3956.html

相关文章

直播弹幕系统(二)- 整合RabbitMQ进行消息广播和异步处理

直播弹幕系统(二)- 整合RabbitMQ进行消息广播和异步处理前言一. Socket服务整合RabbitMQ二. 弹幕服务创建2.1 创建一个公共maven项目2.2 弹幕服务项目创建2.2.1 创建队列和广播型交换机2.2.2 生产者发送最终弹幕数据2.2.3 消费者监听原始弹幕数据2.3 Soc…

Jmeter简单入门

背景 我们项目中一般测试接口都是用vscode中的REST Client插件(推荐好用)或者的话postman(适合写一些脚本和文件的上传) 但是他们都有一个不太行的功能,那就是多线程并发测试,其他市面上的什么apipost也都是不支持,网…

MySQL——count(*)的底层实现以及相关优化

在开发系统的时候,可能需要需要计算一个表的行数这时候你可能会想,一条 select count(*) from t 语句不就解决了吗? 但是,会发现随着系统中记录数越来越多,这条语句执行得也会越来越慢。然后可能就想了,My…

Qt新手入门指南——创建一个基于Qt Widget的文本查找器(二)

Qt是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写,所有平台无差别运行,更提供了几乎所有开发过程中需要用到的工具。如今,Qt已被运用于超过70个行业、数千家企业,支持数百万设备及应用。 本教程将介绍如何使用…

frp内网穿透https

在公网服务器搭建frps(service),在内网本地机子搭建frpc(client),流量通过访问公网ip,经过frps服务端转发到fprc客户端,fprc再转发到本地web应用。 官方下载地址​ https://github.com/fatedier/frp/releases 官方文档地址https…

[山东科技大学OJ]2618 Problem E: 截取字符串

Time Limit: 1 Sec Memory Limit: 2 MB Submit: 1910 Solved: 360 [Submit][Status] Description 对给定的一个字符串,截取其中一部分输出。 Input 输入为两行,第一行为一个字符串s,长度至少为1且不超过20个字符;第二行为两个…

蓝桥杯嵌入式 cubeMX生成代码解读

文章目录前言一、代码风格二、为什么要这些注释?三、生成的独立模块的代码总结前言 本篇文章讲介绍一下cubeMX生成代码的风格。 一、代码风格 在main.c中可以看到非常多的注释代码,很多人都不知道这些是用来干嘛的,现在就给大家介绍一下这…

朴素二进制表示法

思路方案 在安全领域的研究中我们发现,很多数据预处理的步骤,在不同的场景下中都可以相互 借鉴,甚至可以进行直接复用。例如,对于加密流量相关的数据,当算法工程师 获取到一批加密流量的 pcap 包之后,不论他…