最近遇到一個需要用到線性顏色漸變的需求嘱丢,而且后期還可能改為顏色“閃動”的效果挽铁。
預(yù)期效果如下:
于是就去研究了一下線性顏色漸變伟桅,這里做下總結(jié)。
實(shí)現(xiàn)線性顏色漸變叽掘,有四種方式:
1.自定義View繼承自TextView楣铁,獲取View 的Paint對象,并給Paint對象設(shè)置漸變更扁。
2.用canvas#drawText方法盖腕,在onDraw方法中設(shè)置漸變并繪制。
3.用StaticLayout實(shí)現(xiàn)多行文本顏色漸變浓镜。
4.用DynamicLayout實(shí)現(xiàn)多行文本顏色漸變溃列。
下面詳細(xì)說明這四種方式:
1.直接獲取Paint對象,并給Paint設(shè)置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);
}
}
運(yùn)行效果如下圖:
? 代碼很簡單膛薛,就是在onSizeChanged獲取mPaint,并給mPaint設(shè)置線性漸變听隐,然后在onDraw方法里繪制出來。如果你只是想在TextView中顯示漸變顏色的文本相叁,這種方式是最簡單的遵绰。
2.canvas#drawText實(shí)現(xiàn)顏色漸變
這種方式更多用于自定義繪圖或者進(jìn)行圖片處理時繪制文字辽幌。當(dāng)然也可以用于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);
}
}
運(yùn)行結(jié)果如下圖:
需要注意的是椿访,這種方式實(shí)現(xiàn)的漸變文本乌企,是不能換行的。也就是說成玫,不管文本有多長加酵,都只能一行顯示(例子中就是把三行的字符串顯示為一行)
3.用StaticLayout實(shí)現(xiàn)多行文本顏色漸變
StaticLayout是一個用來處理文本換行的控件,可以用它來實(shí)現(xiàn)多行文本顏色漸變哭当。
StaticLayout常用的構(gòu)造函數(shù)是這個:
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 之后猪腕,上面幾個構(gòu)造函數(shù)都將被廢棄,將使用StaticLayout.Builder替代钦勘。StaticLayout.Builder的用法也很簡單陋葡,就是先調(diào)用 StaticLayout#obtain方法構(gòu)造StaticLayout.Builder對象,在調(diào)用Builder對象的一系列seter方法彻采,最后調(diào)用build()方法腐缤。詳細(xì)請看官方文檔:
下面說明一下各個參數(shù)的含義:
source: 要顯示的文本
bufstart:要處理文本的開始字符位置
bufend:要處理文本的結(jié)束字符位置
paint:畫筆對象
width:文本寬度,超過這個區(qū)域會自動換行
outerwidth:換行寬度肛响,超過這個寬度會自動換行
align:對齊方式
spacingmult:行間距岭粤,通常設(shè)置為1.0f,設(shè)置了這個值后特笋,行間距將變?yōu)槟J(rèn)間距乘以這個數(shù)值剃浇,如1.5表示1.5倍行間距
spacingadd:行間距增加值,最終的行間距 = 默認(rèn)間距 * spacingmult + spacingadd
includepad:設(shè)置是否包括超出字體上升和下降的額外空間猎物,一般設(shè)置為true虎囚,設(shè)置了true之后,文本會垂直居中顯示霸奕,可避免在某些多語言下溜宽,文案被裁剪。
ellipsize: 當(dāng)文本超出區(qū)域或者行數(shù)超出限制時质帅,省略的顯示位置
ellipsizedWidth:顯示省略的那一行可顯示文本的寬度适揉,設(shè)置0則那一行顯示為 ...
下面進(jìn)入正題,看下怎么用 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);
}
在創(chuàng)建StaticLayout的時候嫉嘀,把mLinearGradient設(shè)置進(jìn)去,再在onDraw方法中調(diào)用 StaticLayout#draw方法魄揉,就能實(shí)現(xiàn)多行的顏色漸變剪侮。這里需要注意的是,StaticLayout只能處理那些不能被再次編輯的文本,也就是說它處理的文本是固定的瓣俯,不能改變的杰标,如果要處理可以改變的文本,請使用DynamicLayout彩匕。
運(yùn)行效果如下圖所示:
4.用DynamicLayout實(shí)現(xiàn)線性顏色漸變
DynamicLayout也是一個處理文本換行的控件腔剂,它和StaticLayout的用法幾乎一模一樣。區(qū)別就是驼仪,DynamicLayout可以處理可編輯的文本掸犬,也就是說它處理的文本可以改變。關(guān)于它的構(gòu)造方法及參數(shù)的說明請參見StaticLyaout绪爸,此處不再贅述湾碎。
在DynamicLayout的構(gòu)造方法的源碼中,有這么一段代碼(只截取關(guān)鍵代碼)
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));
}
}
當(dāng)傳入構(gòu)造方法的base是實(shí)現(xiàn)了 Spannable接口的實(shí)例時奠货,DynamicLayout會自動給這個base設(shè)置ChangeWatcher監(jiān)聽器介褥,ChangeWatcher實(shí)際上就是一個TextWatcher接口,用于監(jiān)聽base序列的改變递惋。當(dāng)base改變時呻顽,ChangeWatcher#onSpanChanged方法會回調(diào),然后去刷新DynamicLayout的布局丹墨。這也就是為什么DynamicLayout能處理可編輯文本的原因。
說回正題嬉愧,用DynamicLayout實(shí)現(xiàn)多行漸變文本也很簡單贩挣,關(guān)鍵代碼如下:
@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設(shè)置了點(diǎn)擊監(jiān)聽,當(dāng)點(diǎn)擊這個View時没酣,它的文本會增加一行王财,其他的代碼和StaticLayout的相似,不做介紹了裕便。運(yùn)行結(jié)果如下:
當(dāng)點(diǎn)擊圖片后绒净,會出現(xiàn)多一行文本,如圖:
拓展
上面總結(jié)了四種實(shí)現(xiàn)線性顏色漸變的方式偿衰,但是都只能實(shí)現(xiàn)“靜止”的顏色漸變挂疆,沒法實(shí)現(xiàn)“動態(tài)”的漸變。為了以后能 反手給設(shè)計(jì)師一巴掌 (不下翎,是滿足設(shè)計(jì)師的要求)缤言,下面說明如何實(shí)現(xiàn)“動態(tài)的漸變”。
思路也簡單视事,就是讓“靜止的漸變”每隔一定時間胆萧,就位移一定距離,然后刷新重繪俐东,這樣就有動態(tài)的效果了跌穗。下面看看關(guān)鍵代碼:
@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毫秒設(shè)置給LinearGradient對象,然后再刷新重繪蚌吸。這里要注意窝革,要給mTranslate設(shè)置一個范圍(代碼中是設(shè)置 2*mViewWidth),不然mTranslate的值一直增大死讹,可能造成數(shù)值溢出悉默。運(yùn)行效果如下:
以上就是我所總結(jié)的實(shí)現(xiàn)線性顏色漸變的方法。有不同意見的朋友肉迫,歡迎交流指教验辞。