Android 線性顏色漸變

最近遇到一個需要用到線性顏色漸變的需求嘱丢,而且后期還可能改為顏色“閃動”的效果挽铁。
預(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ì)請看官方文檔:

StaticLayout.Builder用法

下面說明一下各個參數(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)線性顏色漸變的方法。有不同意見的朋友肉迫,歡迎交流指教验辞。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市喊衫,隨后出現(xiàn)的幾起案子跌造,更是在濱河造成了極大的恐慌,老刑警劉巖族购,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壳贪,死亡現(xiàn)場離奇詭異,居然都是意外死亡寝杖,警方通過查閱死者的電腦和手機(jī)违施,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瑟幕,“玉大人磕蒲,你說我怎么就攤上這事≈豁铮” “怎么了辣往?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長殖卑。 經(jīng)常有香客問我站削,道長,這世上最難降的妖魔是什么孵稽? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任许起,我火速辦了婚禮,結(jié)果婚禮上菩鲜,老公的妹妹穿的比我還像新娘街氢。我一直安慰自己,他們只是感情好睦袖,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布珊肃。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪伦乔。 梳的紋絲不亂的頭發(fā)上厉亏,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天,我揣著相機(jī)與錄音烈和,去河邊找鬼爱只。 笑死,一個胖子當(dāng)著我的面吹牛招刹,可吹牛的內(nèi)容都是我干的恬试。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼疯暑,長吁一口氣:“原來是場噩夢啊……” “哼训柴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起妇拯,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤幻馁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后越锈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仗嗦,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年甘凭,在試婚紗的時候發(fā)現(xiàn)自己被綠了稀拐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡丹弱,死狀恐怖钩蚊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蹈矮,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布鸣驱,位于F島的核電站泛鸟,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏踊东。R本人自食惡果不足惜北滥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望闸翅。 院中可真熱鬧再芋,春花似錦、人聲如沸坚冀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至司训,卻和暖如春构捡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背壳猜。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工勾徽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人统扳。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓喘帚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親咒钟。 傳聞我的和親對象是個殘疾皇子吹由,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容

  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 6,432評論 0 17
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,190評論 25 707
  • 1盯腌、通過CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請求組件 FMDB本地數(shù)據(jù)庫組件 SD...
    陽明先生_X自主閱讀 15,982評論 3 119
  • 這風(fēng)吹過額頭這雨下在心中季節(jié)不會重復(fù)只能靜靜的流轉(zhuǎn)桃花不知何時笑迎春風(fēng)姍姍來遲的春雨金貴般地灑在我穿過的長街年輕的...
    昊水長天閱讀 242評論 2 5
  • 其實(shí)我們都不悲哀腕够,悲哀的是活在恐懼里级乍。活成了一頭困獸帚湘。
    空畫閱讀 231評論 0 1