Android 仿今日頭條文字漸變效果

概述

本文主要分享仿今日頭條文字漸變效果缀台,效果圖如下:

Screenrecorder-2020-07-15-15-47-30-244.gif

實(shí)現(xiàn)要點(diǎn):

  • 繪制水平垂直居中的文本
  • 采用畫布裁剪實(shí)現(xiàn)漸變效果

繪制水平垂直居中的文本

文本水平居中

文本水平居中有兩種方式:

  • 調(diào)用Paint的setTextAlign(Paint.Align.CENTER)方法
  • 控件寬度 / 2 - 文字寬度 / 2計(jì)算文本的x軸坐標(biāo)

文本垂直居中

文本垂直居中重點(diǎn)在于計(jì)算基準(zhǔn)線,大致過程如下:

  • 獲取View的中心位置
  • 中心位置下移半個(gè)字體的高度
  • 上移descent,達(dá)到文字的最終位置即BaseLine的位置

代碼實(shí)現(xiàn)如下:

  Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
  float baseLine = (getHeight() + fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent;

為了方便理解穴豫,baseLine的計(jì)算异逐,可參考下圖:


966618-20170921162908009-1156220555.png

畫布裁剪實(shí)現(xiàn)漸變效果

文字漸變主要是通過畫布裁剪實(shí)現(xiàn)的仇让,裁剪漸變部分與不變部分再進(jìn)行疊加,從而達(dá)到漸變的效果悼沿,這種實(shí)現(xiàn)方式有一個(gè)好處是防止過度繪制。

關(guān)鍵代碼如下:

//漸變部分
 private void drawGradientText(Canvas canvas) {
        //保存當(dāng)前畫布
        canvas.save();
        //文字水平居中方案二:x = getWidth() / 2 - 文字寬度 / 2
        float textWidth = mTextPaint.measureText(mText, 0, mText.length());
        float x = (getWidth() - textWidth) / 2;

        //文字垂直居中:BaseLine的計(jì)算 = getHeight() / 2 + 文字高度 / 2 - descent

        Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
        float textHeight = fontMetrics.descent - fontMetrics.ascent;
        float y = (getHeight() + textHeight) / 2 - fontMetrics.descent;

        mTextPaint.setColor(Color.RED);

        //先裁剪漸變區(qū)域文字要顯示的區(qū)域
        float right = x + textWidth * mPercent;
        canvas.clipRect(x, 0, right, getHeight());

        //裁剪完成后再繪制文字
        canvas.drawText(mText, x, y, mTextPaint);

        //恢復(fù)畫布
        canvas.restore();
    }
    //不變部分
     private void drawNormalText(Canvas canvas) {
        canvas.save();
        float textWidth = mTextPaint.measureText(mText, 0, mText.length());
        float x = (getWidth() - textWidth) / 2;

        Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
        float textHeight = fontMetrics.descent - fontMetrics.ascent;
        float y = (getHeight() + textHeight) / 2 - fontMetrics.descent;

        mTextPaint.setColor(Color.BLACK);

        //對(duì)不變區(qū)域文字進(jìn)行裁剪骚灸,防止過度繪制(這個(gè)很重要)
        float left = x + textWidth * mPercent;
        canvas.clipRect(left, 0, x + textWidth, getHeight());

        canvas.drawText(mText, x, y, mTextPaint);

        canvas.restore();
    }

仿今日頭條文字漸變效果實(shí)現(xiàn)思路

知道了文本繪制居中與畫布裁剪后糟趾,實(shí)現(xiàn)仿今日頭條文字漸變效果就很簡(jiǎn)單了,大致思路是通過監(jiān)聽ViewPager的滾動(dòng)獲取滾動(dòng)的百分比,再通過滾動(dòng)百分比計(jì)算出漸變區(qū)域與不變區(qū)域并分別進(jìn)行裁剪义郑,最后通過圖層疊效果就出來了蝶柿。
漸變控件關(guān)鍵代碼:

public class ColorChangeTextView extends AppCompatTextView {

    private int mTextSize = sp2px(30);
    private int mTextColor = Color.BLACK;
    private int mTextColorChange = Color.RED;

    private TextPaint mTextPaint;

    private int mDirection = LEFT;//文件漸變方向

    protected static final int LEFT = 0;
    protected static final int RIGHT = 1;
    @IntDef(value = {LEFT, RIGHT})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Direction {
    }

    private float mProgress;//文字漸變的進(jìn)度

    public float getProgress() {
        return mProgress;
    }

    public void setProgress(float mProgress) {
        this.mProgress = mProgress;
        invalidate();
    }

    public void setDirection(@Direction int direction) {
        this.mDirection = direction;
    }

    public ColorChangeTextView(Context context) {
        this(context, null);
    }

    public ColorChangeTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ColorChangeTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttr(context, attrs);
        init();
    }

    private void initAttr(final Context context, @Nullable final AttributeSet attrs) {
        TypedArray ta = context.obtainStyledAttributes(attrs,
                R.styleable.ColorChangeTextView);
        mTextSize = ta.getDimensionPixelSize(
                R.styleable.ColorChangeTextView_text_size, mTextSize);
        mTextColor = ta.getColor(
                R.styleable.ColorChangeTextView_text_color, mTextColor);
        mTextColorChange = ta.getColor(
                R.styleable.ColorChangeTextView_text_color_change, mTextColorChange);
        mProgress = ta.getFloat(R.styleable.ColorChangeTextView_progress, 0);

        ta.recycle();
    }

    private void init() {
        mTextPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG);
        mTextPaint.setColor(mTextColor);
        mTextPaint.setTextSize(mTextSize);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        switch (mDirection){
            case LEFT:
                drawLeft(canvas);
                break;
            case RIGHT:
                drawRight(canvas);
                break;
        }
    }

    private void drawLeft(Canvas canvas){
        //繪制底部不變部分
        canvas.save();

        String text = getText().toString();
        float textWidth = mTextPaint.measureText(text);
        Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
        float textHeight = fontMetrics.descent - fontMetrics.ascent;

        float x = (getWidth() - textWidth) / 2;
        float baseLine = (getHeight() + textHeight) / 2 - fontMetrics.descent;

        float left = x + textWidth * mProgress;
        canvas.clipRect(left,0,x + textWidth,getHeight());
        mTextPaint.setColor(mTextColor);
        canvas.drawText(text,x,baseLine,mTextPaint);

        canvas.restore();

        //繪制頂部漸變部分
        canvas.save();

        float right = x + textWidth * mProgress;
        canvas.clipRect(x,0,right,getHeight());
        mTextPaint.setColor(mTextColorChange);
        canvas.drawText(text,x,baseLine,mTextPaint);

        canvas.restore();
    }

    private void drawRight(Canvas canvas) {

        //繪制頂部漸變部分
        canvas.save();

        String text = getText().toString();
        float textWidth = mTextPaint.measureText(text);
        Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
        float textHeight = fontMetrics.descent - fontMetrics.ascent;
        float baseLine = (getHeight() + textHeight) / 2 - fontMetrics.descent;
        float x = (getWidth() - textWidth) / 2;

        float left = x + textWidth * (1 - mProgress);
        float right = x + textWidth;
        canvas.clipRect(left,0,right,getHeight());
        mTextPaint.setColor(mTextColorChange);
        canvas.drawText(text,x,baseLine,mTextPaint);

        canvas.restore();

        //繪制底部不變部分
        canvas.save();

        right = x + textWidth * (1 - mProgress);
        canvas.clipRect(0,0,right,getHeight());
        mTextPaint.setColor(mTextColor);
        canvas.drawText(text,x,baseLine,mTextPaint);

        canvas.restore();
    }

    static int sp2px(float sp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, Resources.getSystem().getDisplayMetrics());
    }
}

ViewPager滾動(dòng)的關(guān)鍵代碼:

 mViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener(){
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                if(positionOffset > 0){
                    ColorChangeTextView left = mTabs.get(position);
                    ColorChangeTextView right = mTabs.get(position + 1);

                    left.setDirection(ColorChangeTextView.RIGHT);
                    right.setDirection(ColorChangeTextView.LEFT);

                    left.setProgress(1- positionOffset);
                    right.setProgress(positionOffset);
                }
            }
        });

完整代碼實(shí)現(xiàn)

百度鏈接
密碼:k26w

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市非驮,隨后出現(xiàn)的幾起案子交汤,更是在濱河造成了極大的恐慌,老刑警劉巖劫笙,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芙扎,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡填大,警方通過查閱死者的電腦和手機(jī)戒洼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來允华,“玉大人圈浇,你說我怎么就攤上這事±瘢” “怎么了汉额?”我有些...
    開封第一講書人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)榨汤。 經(jīng)常有香客問我蠕搜,道長(zhǎng),這世上最難降的妖魔是什么收壕? 我笑而不...
    開封第一講書人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任妓灌,我火速辦了婚禮,結(jié)果婚禮上蜜宪,老公的妹妹穿的比我還像新娘虫埂。我一直安慰自己,他們只是感情好圃验,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開白布掉伏。 她就那樣靜靜地躺著,像睡著了一般澳窑。 火紅的嫁衣襯著肌膚如雪斧散。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,337評(píng)論 1 310
  • 那天摊聋,我揣著相機(jī)與錄音鸡捐,去河邊找鬼。 笑死麻裁,一個(gè)胖子當(dāng)著我的面吹牛箍镜,可吹牛的內(nèi)容都是我干的源祈。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼色迂,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼香缺!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起脚草,我...
    開封第一講書人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤赫悄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后馏慨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體埂淮,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年写隶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了倔撞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡慕趴,死狀恐怖痪蝇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情冕房,我是刑警寧澤躏啰,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站耙册,受9級(jí)特大地震影響给僵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜详拙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一帝际、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧饶辙,春花似錦蹲诀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至矿微,卻和暖如春痕慢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背冷冗。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工守屉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留惑艇,地道東北人蒿辙。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓拇泛,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親思灌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子俺叭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359