前言
最近开发过程中要实现文本颜色渐变的效果。什么,文本颜色也要渐变?虽然不乐意,但是也只能说好吧...
先来点最终效果图
常规操作
常见的渐变做法有两种,原理其实都是一样的。都是创建一个 LinearGradient 对象,并将其设置到 TextView 的画笔中。
先来简单介绍下 LinearGradient
LinearGradient
Shader 子类,用于实现线性渐变的效果。常用的构造方法如下
public LinearGradient(float x0,
float y0,
float x1,
float y1,
int color0,
int color1,
Shader.TileMode tile)
参数说明
- (x0, y0):渐变起始点坐标
- (x1, y1):渐变结束点坐标
- color0:渐变起始颜色
- color1:渐变终止颜色
- tile:填充模式
- CLAMP:边缘拉伸。使用边缘颜色对区域外的范围进行填充
- REPEAT:重复模式。在水平和垂直两个方向上重复填充
- MIRROR:镜像模式。在水平和垂直两个方向上以镜像的方式重复填充,相邻图像间有间隙
做法一
继承 TextView,重写 onLayout 方法后设置 Shader
public class GradientTextView extends TextView {
...
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
getPaint().setShader(new LinearGradient(0, 0, getWidth(), getHeight(),
startColor,
endColor,
Shader.TileMode.CLAMP));
}
}
}
创建 LinearGradient 时,传入的起始坐标为 (0,0),结束坐标为 (getWidth(), getHeight()),所以渐变效果是从左上角向右下角渐变的。效果如下
也可以改成从上往下渐变的效果 getPaint().setShader(new LinearGradient(0, 0, 0, getHeight(),
startColor,
endColor,
Shader.TileMode.CLAMP));
效果如下
这种做法是为了获取 View 的宽或高作为 LinearGradient 的构造参数。如果渐变效果与 View 的宽或高无关,则无需使用此做法。另外此做法的渐变效果是整体的。如果想要实现 TextView 中每一行文本都渐变的话,可以参考下面的做法二做法二
直接设置 Shader
Shader shader = new LinearGradient(0, 0, 0, textView.getLineHeight(),
Color.RED, Color.BLUE, Shader.TileMode.REPEAT);
textView.getPaint().setShader(shader);
textView.setText("哈喽,benio\n哈喽,benio\n哈喽,benio");
效果如下
多行渐变,效果不错。但是这种做法有一点缺陷,那就是所有文字都变成渐变色了。假设我们只需要部分字符是渐变色的话,这种方式就不太合理了。特别是在一些使用了 Span 的场景下。骚操作
要实现部分文字颜色不一样的话,第一时间我想到的是 Span。先看下官方提供的 ForegroundColorSpan
public class ForegroundColorSpan extends CharacterStyle
implements UpdateAppearance, ParcelableSpan {
private final int mColor;
public ForegroundColorSpan(@ColorInt int color) {
mColor = color;
}
...
/**
* Updates the color of the TextPaint to the foreground color.
*/
@Override
public void updateDrawState(@NonNull TextPaint textPaint) {
// 就是这里改了颜色
textPaint.setColor(mColor);
}
}
可以看到,关键就是 updateDrawState() 方法。我们可以在该方法内实现想要的样式。接下来我们参考 ForegroundColorSpan 的做法,依照上面做法二的思路,实现一个渐变色的 Span
class LinearGradientForegroundSpan extends CharacterStyle implements UpdateAppearance {
private int startColor;
private int endColor;
private int lineHeight;
public LinearGradientForegroundSpan(int startColor, int endColor, int lineHeight) {
this.startColor = startColor;
this.endColor = endColor;
this.lineHeight = lineHeight;
}
@Override
public void updateDrawState(TextPaint tp) {
tp.setShader(new LinearGradient(0, 0, 0, lineHeight,
startColor, endColor, Shader.TileMode.REPEAT));
}
}
测试代码
SpannableString part1 = new SpannableString("哈喽,");
part1.setSpan(new LinearGradientForegroundSpan(Color.RED, Color.LTGRAY, textView.getLineHeight()),
0, part1.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
SpannableStringBuilder sb = new SpannableStringBuilder();
sb.append(part1);
sb.append("benio\n");
SpannableString part2 = new SpannableString("哈喽,benio\n哈喽,benio");
part2.setSpan(new LinearGradientForegroundSpan(Color.RED, Color.LTGRAY, textView.getLineHeight()),
0, part2.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
sb.append(part2);
textView.setText(sb);
效果如下
如果还不能满足你的需求,可以将 Shader 作为参数传入 Span 中class ShaderForegroundSpan extends CharacterStyle implements UpdateAppearance {
private Shader mShader;
public ShaderForegroundSpan(Shader shader) {
mShader = shader;
}
@Override
public void updateDrawState(TextPaint tp) {
tp.setShader(mShader);
}
}
小结
文本颜色渐变的原理都是通过创建一个 LinearGradient 对象,然后其设置到 TextView 的画笔中实现的。构造 LinearGradient 的参数不同,渐变效果也不一样
- 做法一:渐变效果与 View 的宽或高相关。适用于所有文本整体渐变的场景
- 做法二:渐变效果与行相关,每行的渐变效果一致。适用于每行文本渐变效果一致的场景
- 做法三:用 Span 来实现,适用于局部文本渐变,多行文本渐变的场景