最近遇到一个需要用到线性颜色渐变的需求,而且后期还可能改为颜色“闪动”的效果。
预期效果如下:
于是就去研究了一下线性颜色渐变,这里做下总结。
实现线性颜色渐变,有四种方式:
1.自定义View继承自TextView,获取View 的Paint对象,并给Paint对象设置渐变。
2.用canvas#drawText方法,在onDraw方法中设置渐变并绘制。
3.用StaticLayout实现多行文本颜色渐变。
4.用DynamicLayout实现多行文本颜色渐变。
下面详细说明这四种方式:
1.直接获取Paint对象,并给Paint设置LinearGradient
public class LinearGradientTextView extends TextView {
private LinearGradient mLinearGradient;
private Paint mPaint;
private int mViewWidth = 0;
public LinearGradientTextView1(Context context) {
this(context, null);
}
public LinearGradientTextView1(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LinearGradientTextView1(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mViewWidth == 0) {
mViewWidth = getMeasuredWidth();
mLinearGradient = new LinearGradient(0, 0, mViewWidth, 0,
new int[]{0xFFFA3DB3, 0xFF3D53FB}, null,
Shader.TileMode.REPEAT);
mPaint = getPaint();
mPaint.setShader(mLinearGradient);
}
}
@Override
protected void onDraw(Canvas canvas) {
setGravity(Gravity.LEFT);
super.onDraw(canvas);
}
}
运行效果如下图:
代码很简单,就是在onSizeChanged获取mPaint,并给mPaint设置线性渐变,然后在onDraw方法里绘制出来。如果你只是想在TextView中显示渐变颜色的文本,这种方式是最简单的。
2.canvas#drawText实现颜色渐变
这种方式更多用于自定义绘图或者进行图片处理时绘制文字。当然也可以用于TextView 绘制渐变文本。下面给出的例子是在ImageView中绘制颜色渐变的文本:
public class GradientImageView extends ImageView {
private LinearGradient mLinearGradient;
private Paint mPaint;
private int mViewWidth = 0;
private String mSrcString;
public GradientImageView(Context context) {
this(context, null);
}
public GradientImageView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public GradientImageView(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mViewWidth == 0) {
mViewWidth = getMeasuredWidth();
mLinearGradient = new LinearGradient(0, 0, mViewWidth, 0,
new int[]{0xFFFA3DB3, 0xFF3D53FB}, null,
Shader.TileMode.REPEAT);
mPaint = new Paint();
mPaint.setTextSize(ScreenUtil.dpToPx(getResources(), 16));
mPaint.setShader(mLinearGradient);
mSrcString = "there are several linearGradient lines:+" + "\n" +
"This is the first line of gradient text" + "\n" +
"this is the second line of gradient text";
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawText(mSrcString, 10, 300, mPaint);
}
}
运行结果如下图:
需要注意的是,这种方式实现的渐变文本,是不能换行的。也就是说,不管文本有多长,都只能一行显示(例子中就是把三行的字符串显示为一行)
3.用StaticLayout实现多行文本颜色渐变
StaticLayout是一个用来处理文本换行的控件,可以用它来实现多行文本颜色渐变。
StaticLayout常用的构造函数是这个:
public StaticLayout (CharSequence source,
TextPaint paint,
int width,
Layout.Alignment align,
float spacingmult,
float spacingadd,
boolean includepad)
还有两个不常用的:
public StaticLayout(CharSequence source, int bufstart, int bufend, TextPaint paint,
int outerwidth, Layout.Alignment align, float spacingmult,
float spacingadd, boolean includepad)
public StaticLayout(CharSequence source, int bufstart, int bufend, TextPaint paint,
int outerwidth, Layout.Alignment align, float spacingmult,
float spacingadd, boolean includepad, TextUtils.TruncateAt ellipsize,
int ellipsizedWidth)
在android api 28 之后,上面几个构造函数都将被废弃,将使用StaticLayout.Builder替代。StaticLayout.Builder的用法也很简单,就是先调用 StaticLayout#obtain方法构造StaticLayout.Builder对象,在调用Builder对象的一系列seter方法,最后调用build()方法。详细请看官方文档:
下面说明一下各个参数的含义:
source: 要显示的文本
bufstart:要处理文本的开始字符位置
bufend:要处理文本的结束字符位置
paint:画笔对象
width:文本宽度,超过这个区域会自动换行
outerwidth:换行宽度,超过这个宽度会自动换行
align:对齐方式
spacingmult:行间距,通常设置为1.0f,设置了这个值后,行间距将变为默认间距乘以这个数值,如1.5表示1.5倍行间距
spacingadd:行间距增加值,最终的行间距 = 默认间距 * spacingmult + spacingadd
includepad:设置是否包括超出字体上升和下降的额外空间,一般设置为true,设置了true之后,文本会垂直居中显示,可避免在某些多语言下,文案被裁剪。
ellipsize: 当文本超出区域或者行数超出限制时,省略的显示位置
ellipsizedWidth:显示省略的那一行可显示文本的宽度,设置0则那一行显示为 ...
下面进入正题,看下怎么用 StaticLayout显示多行颜色渐变,下面是主要的代码:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mViewWidth == 0) {
mViewWidth = getMeasuredWidth();
mLinearGradient = new LinearGradient(0, 0, mViewWidth, 0,
new int[]{0xFFD70F00, 0xFFD53A02, 0xFFDB9501, 0xFF3C9B03,
0xFF04C0AF, 0xFF020098, 0xFF4C0177}, null,
Shader.TileMode.REPEAT);
mSrcString = getResources().getString(R.string.home_lineargradient_text);
mTextPaint = new TextPaint();
mTextPaint.setTextSize(ScreenUtil.dpToPx(getResources(), 20));
mTextPaint.setShader(mLinearGradient);
mStaticLayout = new StaticLayout(mSrcString, 0, 120, mTextPaint, mViewWidth,
Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true,
TextUtils.TruncateAt.END,
ScreenUtil.dpToPx(getResources(), 10));
}
}
@Override
protected void onDraw(Canvas canvas) {
setGravity(Gravity.LEFT);
mStaticLayout.draw(canvas);
}
在创建StaticLayout的时候,把mLinearGradient设置进去,再在onDraw方法中调用 StaticLayout#draw方法,就能实现多行的颜色渐变。这里需要注意的是,StaticLayout只能处理那些不能被再次编辑的文本,也就是说它处理的文本是固定的,不能改变的,如果要处理可以改变的文本,请使用DynamicLayout。
运行效果如下图所示:
4.用DynamicLayout实现线性颜色渐变
DynamicLayout也是一个处理文本换行的控件,它和StaticLayout的用法几乎一模一样。区别就是,DynamicLayout可以处理可编辑的文本,也就是说它处理的文本可以改变。关于它的构造方法及参数的说明请参见StaticLyaout,此处不再赘述。
在DynamicLayout的构造方法的源码中,有这么一段代码(只截取关键代码)
public DynamicLayout(CharSequence base, CharSequence display,
TextPaint paint,
int width, Alignment align, TextDirectionHeuristic textDir,
float spacingmult, float spacingadd,
boolean includepad, int breakStrategy, int hyphenationFrequency,
int justificationMode, TextUtils.TruncateAt ellipsize,
int ellipsizedWidth) {
.......
if (base instanceof Spannable) {
if (mWatcher == null)
mWatcher = new ChangeWatcher(this);
// Strip out any watchers for other DynamicLayouts.
Spannable sp = (Spannable) base;
ChangeWatcher[] spans = sp.getSpans(0, sp.length(), ChangeWatcher.class);
for (int i = 0; i < spans.length; i++)
sp.removeSpan(spans[i]);
sp.setSpan(mWatcher, 0, base.length(),
Spannable.SPAN_INCLUSIVE_INCLUSIVE |
(PRIORITY << Spannable.SPAN_PRIORITY_SHIFT));
}
}
当传入构造方法的base是实现了 Spannable接口的实例时,DynamicLayout会自动给这个base设置ChangeWatcher监听器,ChangeWatcher实际上就是一个TextWatcher接口,用于监听base序列的改变。当base改变时,ChangeWatcher#onSpanChanged方法会回调,然后去刷新DynamicLayout的布局。这也就是为什么DynamicLayout能处理可编辑文本的原因。
说回正题,用DynamicLayout实现多行渐变文本也很简单,关键代码如下:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mViewWidth == 0) {
mViewWidth = getMeasuredWidth();
mLinearGradient = new LinearGradient(0, 0, mViewWidth, 0,
new int[]{0xFFD70F00, 0xFFD53A02, 0xFFDB9501, 0xFF3C9B03,
0xFF04C0AF, 0xFF020098, 0xFF4C0177}, null,
Shader.TileMode.REPEAT);
mSrcString = getResources().getString(R.string.home_lineargradient_text);
mSpannableStringBuilder = new SpannableStringBuilder(mSrcString+"\n");
mTextPaint = new TextPaint();
mTextPaint.setTextSize(ScreenUtil.dpToPx(getResources(), 16));
mTextPaint.setShader(mLinearGradient);
mDynamicLayout = new DynamicLayout(mSpannableStringBuilder,
mTextPaint, mViewWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true);
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mSpannableStringBuilder.append("crazy English liyang !" + "\n");
postInvalidate();
}
});
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(10, 300);
mDynamicLayout.draw(canvas);
}
代码中给这个View设置了点击监听,当点击这个View时,它的文本会增加一行,其他的代码和StaticLayout的相似,不做介绍了。运行结果如下:
当点击图片后,会出现多一行文本,如图:
拓展
上面总结了四种实现线性颜色渐变的方式,但是都只能实现“静止”的颜色渐变,没法实现“动态”的渐变。为了以后能 反手给设计师一巴掌 (不,是满足设计师的要求),下面说明如何实现“动态的渐变”。
思路也简单,就是让“静止的渐变”每隔一定时间,就位移一定距离,然后刷新重绘,这样就有动态的效果了。下面看看关键代码:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (mViewWidth == 0) {
mViewWidth = getMeasuredWidth();
mLinearMatrix = new Matrix();
mLinearGradient = new LinearGradient(0, 0, mViewWidth, 0,
new int[]{0xFFD70F00, 0xFFD53A02, 0xFFDB9501, 0xFF3C9B03, 0xFF04C0AF, 0xFF020098, 0xFF4C0177}, null,
Shader.TileMode.REPEAT);
mSrcString = getResources().getString(R.string.home_lineargradient_text);
mTextPaint = new TextPaint();
mTextPaint.setTextSize(ScreenUtil.dpToPx(getResources(), 16));
mTextPaint.setShader(mLinearGradient);
mStaticLayout = new StaticLayout(mSrcString, mTextPaint, mViewWidth,
Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mLinearMatrix != null) {
mTranslate += mViewWidth / 5;
if (mTranslate > 2* mViewWidth) {
mTranslate = -mViewWidth;
}
mLinearMatrix.setTranslate(mTranslate, 0);
mLinearGradient.setLocalMatrix(mLinearMatrix);
canvas.translate(10, 300);
mStaticLayout.draw(canvas);
postInvalidateDelayed(50);
}
}
这里通过Matrix对象,把位移每隔50毫秒设置给LinearGradient对象,然后再刷新重绘。这里要注意,要给mTranslate设置一个范围(代码中是设置 2*mViewWidth),不然mTranslate的值一直增大,可能造成数值溢出。运行效果如下:
以上就是我所总结的实现线性颜色渐变的方法。有不同意见的朋友,欢迎交流指教。