自定義 View 之仿 QQ 步數(shù)變動(dòng)動(dòng)畫(huà)效果

博主聲明:

轉(zhuǎn)載請(qǐng)?jiān)陂_(kāi)頭附加本文鏈接及作者信息币喧,并標(biāo)記為轉(zhuǎn)載变汪。本文由博主 威威喵 原創(chuàng),請(qǐng)多支持與指教催首。

本文首發(fā)于此 博主威威喵 | 博客主頁(yè)https://blog.csdn.net/smile_running

今天在打開(kāi) QQ 的時(shí)候扶踊,我本人一般都是不怎么去看消息的,除了各大群里面的消息刷屏翅帜,也很少聊天姻檀。其實(shí),我個(gè)人不太喜歡在網(wǎng)絡(luò)上聊天涝滴,除了有什么事情要說(shuō)绣版,那倒是情有可原胶台。如果就閑聊、扯淡的話(huà)杂抽,那等到有時(shí)間诈唬,幾個(gè)人坐下來(lái)喝喝茶,聊聊天不是更好嘛缩麸。雖然我高中也喜歡聊天铸磅,到大學(xué)了,就真的很少和別人在網(wǎng)絡(luò)上聊天了杭朱。也許是年齡越來(lái)越大了阅仔,融入不了小年輕的世界啦,哈哈弧械,我 TM 才 21 歲啊八酒,老了老了。刃唐。羞迷。

好了,瞎扯淡的話(huà)就到這里画饥。也許是我今天走了挺多路程的衔瓮,不知咋地了,QQ 運(yùn)動(dòng)忽然對(duì)我說(shuō) “今天狀態(tài)爆表”抖甘,我趕緊點(diǎn)進(jìn)去看了一下热鞍,不知不覺(jué)今天都走了 2W 多步了,不過(guò)就是腳有點(diǎn)酸衔彻。我又點(diǎn)進(jìn)去看了一下碍现,咦,這個(gè)步數(shù)的動(dòng)畫(huà)效果很不錯(cuò)啊米奸。之前雖然也看過(guò)了,但都沒(méi)在意爽篷,今天突然來(lái)興趣了悴晰,總想試試如何實(shí)現(xiàn)這個(gè)效果。那么逐工,說(shuō)干就干吧铡溪。

首先呢,QQ 步數(shù)動(dòng)畫(huà)是這樣一個(gè)效果泪喊,它的外圈是一條弧線(xiàn)棕硫,中間是步數(shù)的值,隨著這個(gè)值的增加袒啼,那么弧線(xiàn)的弧度也會(huì)隨之遞增哈扮,直接來(lái)看我實(shí)現(xiàn)的效果圖吧纬纪,更加形象:

image
效果圖

好了,就是這樣的一個(gè)效果滑肉,在很多 app 里面都很常見(jiàn)的包各,好像支付寶的錢(qián)錢(qián)動(dòng)畫(huà)也是類(lèi)似的效果,一個(gè)值會(huì)在變化靶庙,直到值為最終結(jié)果為止问畅。那么我們接下來(lái)看看如何實(shí)現(xiàn)這個(gè)效果吧。

首先呢六荒,這肯定是一個(gè)自定義 View护姆,我們需要獲取有它的一個(gè)步數(shù)值、最大步數(shù)值掏击、弧線(xiàn)卵皂、弧線(xiàn)的寬度、不同的顏色值等這幾個(gè)屬性铐料,那我們就可以將它定義為一個(gè) attrs.xml 文件渐裂,在 View 標(biāo)簽中用屬性來(lái)設(shè)置值。第一步钠惩,在 res — value 下新建一個(gè) attrs.xml 文件柒凉,其中代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="QQStepsView">
        <!-- 步數(shù)文本的顏色 -->
        <attr name="step_text_color" format="color" />
        <!-- 步數(shù)文本字體的大小 -->
        <attr name="step_text_size" format="dimension" />
        <!-- 步數(shù)文本字體的大小 -->
        <attr name="step_text_spacing" format="float" />
        <!-- 步數(shù)的最大值 -->
        <attr name="step_max_value" format="float" />
        <!-- 弧的顏色 -->
        <attr name="step_arc_color" format="color" />
        <!-- 進(jìn)度的顏色 -->
        <attr name="step_progress_color" format="color" />
        <!-- 弧的寬度 -->
        <attr name="step_arc_width" format="dimension" />
        <!-- 弧開(kāi)始的角度 -->
        <attr name="arc_start_angle" format="float" />
        <!-- 弧開(kāi)始的角度 -->
        <attr name="arc_sweep_angle" format="float" />
    </declare-styleable>
</resources>

上面代碼我都標(biāo)了屬性作用,也就不要我再解釋了吧篓跛。然后要在哪里獲取這些自定義屬性呢膝捞?肯定是在我們的自定義 View 里面,我們繼承 View 時(shí)都會(huì)默認(rèn)添加三個(gè)構(gòu)造函數(shù)愧沟,就是在構(gòu)造函數(shù)里面去獲取這些值的蔬咬。

    public QQStepsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttrs(context, attrs);
        init();
    }

    private void initAttrs(Context context, AttributeSet attrs) {
        if (attrs != null) {
            TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.QQStepsView);
            mArcColor = array.getColor(R.styleable.QQStepsView_step_arc_color, Color.LTGRAY);
            mStepsColor = array.getColor(R.styleable.QQStepsView_step_progress_color, Color.RED);
            mTextColor = array.getColor(R.styleable.QQStepsView_step_text_color, Color.RED);
            mStartAngle = array.getFloat(R.styleable.QQStepsView_arc_start_angle, -225f);
            mSweepAngle = array.getFloat(R.styleable.QQStepsView_arc_start_angle, 270f);
            mStepsWidth = array.getFloat(R.styleable.QQStepsView_step_arc_width, dp2px(10f));
            mTextSize = array.getDimension(R.styleable.QQStepsView_step_text_size, dp2px(28f));
            mTextSpacing = array.getFloat(R.styleable.QQStepsView_step_text_spacing, dp2px(0.02f));
            mMaxSteps = array.getFloat(R.styleable.QQStepsView_step_max_value, 20000f);
            array.recycle();
        }
    }

下面的話(huà),就要去測(cè)量 View 的寬和高了沐寺。根據(jù)這種情況林艘,我們 View 的寬和高要一致才行,不然的話(huà)圓弧會(huì)變成橢圓的弧混坞,就不好看了狐援,代碼如下:

    @SuppressLint("DrawAllocation")
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST || MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
            mWidth = mHeight = 200;
        } else {
            mWidth = MeasureSpec.getSize(widthMeasureSpec);
            mHeight = MeasureSpec.getSize(heightMeasureSpec);
            if (mWidth != mHeight) {
                mHeight = mWidth = Math.min(mWidth, mHeight);
            }
        }

        mCenterX = mWidth / 2;
        mCenterY = mHeight / 2;

        float paintSize = mArcPaint.getStrokeWidth() / 2;
        mRect = new RectF(paintSize, paintSize, mWidth - paintSize, mHeight - paintSize);
    }

最后,就是繪制圖像了究孕。一個(gè)是弧線(xiàn)的繪制啥酱,一個(gè)是步數(shù)文本的繪制,這部分沒(méi)啥好解釋的厨诸,就是計(jì)算一下坐標(biāo)而已镶殷,代碼如下:

    private void drawSteps(Canvas canvas) {
        // 繪制空的弧
        canvas.drawArc(mRect, mStartAngle, mSweepAngle, false, mArcPaint);
        //繪制步數(shù)的弧
        mStepScale = mSteps / mMaxSteps;
        canvas.drawArc(mRect, mStartAngle, mStepScale * mSweepAngle, false, mStepsPaint);
    }

    private void drawStepsText(Canvas canvas) {
        String steps = String.valueOf(mSteps);
        mBounds = new Rect();
        mStepsTextPaint.getTextBounds(steps, 0, steps.length(), mBounds);

        Paint.FontMetrics metrics = mStepsTextPaint.getFontMetrics();
        canvas.drawText(steps, mCenterX - mBounds.width() / 2, mCenterY - (metrics.top + metrics.bottom) / 2, mStepsTextPaint);
    }

這樣的話(huà),這 QQ 步數(shù)效果所需的關(guān)鍵性代碼都寫(xiě)完了微酬,那我直接放出所以代碼:

package nd.no.xww.qqmessagedragview;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

/**
 * @author xww
 * @desciption : QQ 步數(shù)動(dòng)畫(huà)效果
 * @date 2018/7/26
 * @time 16:48
 * @威威喵
 */
public class QQStepsView extends View {

    private Paint mArcPaint;
    private Paint mStepsPaint;
    private Paint mStepsTextPaint;

    private float mStepsWidth;
    private float mStartAngle;
    private float mSweepAngle;
    private int mTextColor;
    private int mArcColor;
    private int mStepsColor;
    private float mTextSize;
    private float mTextSpacing;
    private int mSteps;
    private float mMaxSteps;
    private float mStepScale;

    private RectF mRect;
    private Rect mBounds;

    private float mWidth;
    private float mHeight;
    private float mCenterX;
    private float mCenterY;

    private void init() {
        initPaint();
    }

    private void initAttrs(Context context, AttributeSet attrs) {
        if (attrs != null) {
            TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.QQStepsView);
            mArcColor = array.getColor(R.styleable.QQStepsView_step_arc_color, Color.LTGRAY);
            mStepsColor = array.getColor(R.styleable.QQStepsView_step_progress_color, Color.RED);
            mTextColor = array.getColor(R.styleable.QQStepsView_step_text_color, Color.RED);
            mStartAngle = array.getFloat(R.styleable.QQStepsView_arc_start_angle, -225f);
            mSweepAngle = array.getFloat(R.styleable.QQStepsView_arc_start_angle, 270f);
            mStepsWidth = array.getDimension(R.styleable.QQStepsView_step_arc_width, dp2px(10f));
            mTextSize = array.getDimension(R.styleable.QQStepsView_step_text_size, dp2px(28f));
            mTextSpacing = array.getFloat(R.styleable.QQStepsView_step_text_spacing, dp2px(0.02f));
            mMaxSteps = array.getFloat(R.styleable.QQStepsView_step_max_value, 20000f);
            array.recycle();
        }
    }

    private void initPaint() {
        mArcPaint = getPaint(mArcColor);
        mStepsPaint = getPaint(mStepsColor);

        mStepsTextPaint = getPaint(mTextColor);
        mStepsTextPaint.setStyle(Paint.Style.FILL);
        mStepsTextPaint.setTextSize(mTextSize);
        mStepsTextPaint.setLetterSpacing(mTextSpacing);
        mStepsTextPaint.setTypeface(Typeface.MONOSPACE);
    }

    private Paint getPaint(int color) {
        Paint paint = new Paint();
        paint.setDither(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(mStepsWidth);
        paint.setAntiAlias(true);
        paint.setColor(color);
        //設(shè)置弧度兩端為圓滑
        paint.setStrokeCap(Paint.Cap.ROUND);
        return paint;
    }

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

    public QQStepsView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public QQStepsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttrs(context, attrs);
        init();
    }

    @SuppressLint("DrawAllocation")
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST || MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
            mWidth = mHeight = 200;
        } else {
            mWidth = MeasureSpec.getSize(widthMeasureSpec);
            mHeight = MeasureSpec.getSize(heightMeasureSpec);
            if (mWidth != mHeight) {
                mHeight = mWidth = Math.min(mWidth, mHeight);
            }
        }

        mCenterX = mWidth / 2;
        mCenterY = mHeight / 2;

        float paintSize = mArcPaint.getStrokeWidth() / 2;
        mRect = new RectF(paintSize, paintSize, mWidth - paintSize, mHeight - paintSize);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        drawSteps(canvas);

        drawStepsText(canvas);
    }

    private void drawSteps(Canvas canvas) {
        // 繪制空的弧
        canvas.drawArc(mRect, mStartAngle, mSweepAngle, false, mArcPaint);
        //繪制步數(shù)的弧
        mStepScale = mSteps / mMaxSteps;
        canvas.drawArc(mRect, mStartAngle, mStepScale * mSweepAngle, false, mStepsPaint);
    }

    private void drawStepsText(Canvas canvas) {
        String steps = String.valueOf(mSteps);
        mBounds = new Rect();
        mStepsTextPaint.getTextBounds(steps, 0, steps.length(), mBounds);

        Paint.FontMetrics metrics = mStepsTextPaint.getFontMetrics();
        canvas.drawText(steps, mCenterX - mBounds.width() / 2, mCenterY - (metrics.top + metrics.bottom) / 2, mStepsTextPaint);
    }

    public void setSteps(int steps) {
        this.mSteps = steps;
        invalidate();
    }

    public void setMaxSteps(float maxSteps) {
        this.mMaxSteps = maxSteps;
    }

    private float dp2px(float dp) {
        return dp * getResources().getDisplayMetrics().density;
    }
}

上面就是完整代碼绘趋,那使用起來(lái)也非常簡(jiǎn)單颤陶,比如你可以這樣:

    <nd.no.xww.qqmessagedragview.QQStepsView
        android:id="@+id/qqstepsview"
        android:layout_width="200dp"
        app:step_arc_color="#D15FEE"
        app:step_max_value="40000"
        app:step_progress_color="#FF83FA"
        app:step_text_color="#B23AEE"
        app:step_text_size="25sp"
        android:layout_height="200dp"
        android:layout_gravity="center" />

這里要記得添加一下 app 的一個(gè)命名空間,否則是出現(xiàn)不了我們自定義的屬性的埋心。這個(gè)命名空間的話(huà)指郁,在上一層 View 下打入 app 就會(huì)跑出來(lái)啦。最后拷呆,也是至關(guān)重要的一步闲坎,動(dòng)畫(huà)效果的設(shè)定,上面僅僅是一個(gè)靜態(tài)的 View茬斧,要想讓它動(dòng)起來(lái)腰懂,還需要加入以下代碼方可呈現(xiàn)動(dòng)畫(huà):

        qqStepsView = findViewById(R.id.qqstepsview);
        ValueAnimator valueAnimator = ObjectAnimator.ofInt(0, 26666);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                qqStepsView.setSteps((int) animation.getAnimatedValue());
            }
        });
        valueAnimator.setDuration(2000);
        valueAnimator.setInterpolator(new AccelerateInterpolator());
        valueAnimator.start();

解釋一下上面的代碼,在 MainActivity 中綁定一下 id 拿到實(shí)例项秉,然后給它設(shè)置一個(gè)值動(dòng)畫(huà)绣溜,給這個(gè)動(dòng)畫(huà)添加一個(gè)值的監(jiān)聽(tīng),它會(huì)在上面設(shè)置的 Duration(2S)內(nèi)從 0 變化到 26666 的值娄蔼,然后我們?cè)O(shè)置一個(gè)步數(shù)就好了怖喻,那么 View 就會(huì)一直跟隨著它的變化直到動(dòng)畫(huà)結(jié)束。

切換一下顏色和字體大小等岁诉,都非常的簡(jiǎn)單锚沸,像使用 TextView 一樣,看看效果

image

好了涕癣,這樣就可以完成一個(gè)效果很不錯(cuò)的動(dòng)畫(huà)了哗蜈,所以你在學(xué)會(huì)了這種值變化動(dòng)畫(huà)后,對(duì)于那些支付寶金額坠韩、QQ步數(shù)等一些數(shù)值變化的動(dòng)畫(huà)還不是手到擒來(lái)距潘。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市只搁,隨后出現(xiàn)的幾起案子音比,更是在濱河造成了極大的恐慌,老刑警劉巖氢惋,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件硅确,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡明肮,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)缭付,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)柿估,“玉大人,你說(shuō)我怎么就攤上這事陷猫★啵” “怎么了的妖?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)足陨。 經(jīng)常有香客問(wèn)我嫂粟,道長(zhǎng),這世上最難降的妖魔是什么墨缘? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任星虹,我火速辦了婚禮,結(jié)果婚禮上镊讼,老公的妹妹穿的比我還像新娘宽涌。我一直安慰自己,他們只是感情好蝶棋,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布卸亮。 她就那樣靜靜地躺著,像睡著了一般玩裙。 火紅的嫁衣襯著肌膚如雪兼贸。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,578評(píng)論 1 305
  • 那天吃溅,我揣著相機(jī)與錄音溶诞,去河邊找鬼。 笑死罕偎,一個(gè)胖子當(dāng)著我的面吹牛很澄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播颜及,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼甩苛,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了俏站?” 一聲冷哼從身側(cè)響起讯蒲,我...
    開(kāi)封第一講書(shū)人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎肄扎,沒(méi)想到半個(gè)月后墨林,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡犯祠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年旭等,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衡载。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡搔耕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出痰娱,到底是詐尸還是另有隱情弃榨,我是刑警寧澤菩收,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站鲸睛,受9級(jí)特大地震影響娜饵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜官辈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一箱舞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧钧萍,春花似錦褐缠、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至万搔,卻和暖如春胡桨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瞬雹。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工昧谊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人酗捌。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓呢诬,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親胖缤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子尚镰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

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