最近遇到一个需要用到线性颜色渐变的需求,而且后期还可能改为颜色“闪动”的效果。
预期效果如下:
于是就去研究了一下线性颜色渐变,这里做下总结。
实现线性颜色渐变,有四种方式:
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, i
nt 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()方法。
详细请看官方文档: StaticLayout.Builder用法
下面说明一下各个参数的含义:
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(), 20));
mTextPaint.setShader(mLinearGradient);
mStaticLayout = new StaticLayout(mSrcString, mTextPaint, mViewWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true);
}
}
@Override
protected void onDraw(Canvas canvas) {
if (mLinearMatrix != null) {
mTranslate += mViewWidth / 5;
if (mTranslate > 2 * mViewWidth) {
mTranslate = -mViewWidth;
}
mLinearMatrix.setTranslate(mTranslate, 0);
mLinearGradient.setLocalMatrix(mLinearMatrix);
canvas.translate(0, 10);
mStaticLayout.draw(canvas);
postInvalidateDelayed(50);
}
}
这里通过Matrix对象,把位移每隔50毫秒设置给LinearGradient对象,然后再刷新重绘。这里要注意,要给mTranslate设置一个范围(代码中是设置 2*mViewWidth),不然mTranslate的值一直增大,可能造成数值溢出。
运行效果如下:
以上就是我所总结的全部内容,有不同意见的朋友,欢迎交流指教。