Android自定義View專題六-PathMeasure實(shí)戰(zhàn),仿小紅書(shū)動(dòng)態(tài)標(biāo)簽

1分析小紅書(shū)的效果

首先看一下這個(gè)效果


reabook.gif

可能看著有一些卡頓,這是由于上傳gif大小有限制遂鹊,壓縮過(guò)度造成的卡頓桥胞。實(shí)際上是很流暢的野舶。
要實(shí)現(xiàn)一些效果槐雾,我們首先要分析這個(gè)效果的組成部分。就像我們平時(shí)寫(xiě)程序是一樣的若河,一個(gè)模塊的整體功能是由若干個(gè)小功能構(gòu)成的能岩。只要分析出了這個(gè)動(dòng)畫(huà)的組成部分接下來(lái)就好做了。
1)首先我們可以看見(jiàn)這個(gè)動(dòng)畫(huà)展開(kāi)以后萧福,中間有一個(gè)小圓的大小是不變的拉鹃,這個(gè)比較簡(jiǎn)單直接畫(huà)出來(lái)就行了。
2)展開(kāi)以后會(huì)有一個(gè)漸變的圓鲫忍,圓起始半徑和中間的小圓一樣膏燕,這個(gè)圓半徑變大的同時(shí)透明度也在不斷的變化。并且這個(gè)動(dòng)畫(huà)是不斷的循環(huán)的悟民。這個(gè)動(dòng)畫(huà)可以用屬性動(dòng)畫(huà)坝辫,使圓的半徑不斷的變化,然后不斷的重繪射亏。
3)還有就是路徑的動(dòng)畫(huà)近忙,路徑是不斷變化的竭业,如果有什么方法可以實(shí)時(shí)改變路徑長(zhǎng)度就好了?這時(shí)候就要用到我們上一篇博客中提到的PathMeasure银锻。
4)在動(dòng)畫(huà)結(jié)束的時(shí)候?qū)⑽淖诛@示出來(lái)永品。
分析了整個(gè)效果的組成,那么接下來(lái)就開(kāi)始實(shí)現(xiàn)吧击纬!

2.實(shí)現(xiàn)

  • 畫(huà)筆路徑的初始化
    /**
     * 中心圓的畫(huà)筆
     */
    private Paint mPaint;
    /**
     * 漸變圓的畫(huà)筆
     */
    private Paint mPaint1;
    /**
     * 路徑的畫(huà)筆
     */
    private Paint mPaint2;
    /**
     * 文字的畫(huà)筆
     */
    private Paint mPaintText;
    /**
     * 初始化路徑,這個(gè)路徑?jīng)]有真正的繪制
     */
    private Path mInitPath;
    /**
     * 用getSegment獲取的路徑片段
     */
    private Path mPath;

    public ReadBook1(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(Color.WHITE);

        mPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint1.setStyle(Paint.Style.FILL);
        mPaint1.setColor(Color.WHITE);

        mPaint3 = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint3.setStyle(Paint.Style.STROKE);
        mPaint3.setColor(Color.WHITE);
        mPaint3.setStrokeWidth(4);


        mPaintText = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintText.setStyle(Paint.Style.STROKE);
        mPaintText.setColor(Color.WHITE);
        mPaintText.setAlpha(0);
        mPaintText.setTextAlign(Paint.Align.CENTER);

        mInitPath = new Path();
        mPath = new Path();

    }

畫(huà)筆的初始化千萬(wàn)不要放到onDraw()方法中進(jìn)行钾麸,因?yàn)閛nDraw方法會(huì)被調(diào)用很多次更振,會(huì)new出大量不必要的對(duì)象。
接下來(lái)是繪制中間的圓

canvas.drawCircle(mWidth / 2, mHeight / 2, 10, mPaint1);
  • 繪制漸變的圓
canvas.drawCircle(mWidth / 2, mHeight / 2, mRadius, mPaint);

public void startCircleAnim() {
        mAnimatorCircle = ValueAnimator.ofFloat(10, 50);
        final float per = 255f / 40f;
        mAnimatorCircle.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float animatedValue = (float) animation.getAnimatedValue();
                mRadius = (int) animatedValue;
                int a = (int) ((40 - (animatedValue - 10)) * per);
                mPaint.setAlpha(a);
                invalidate();
            }
        });
        mAnimatorCircle.setRepeatMode(ValueAnimator.RESTART);
        mAnimatorCircle.setRepeatCount(ValueAnimator.INFINITE);
        mAnimatorCircle.setDuration(3000);
        mAnimatorCircle.start();
    }

我這里講漸變半徑設(shè)置為10-50饭尝, final float per = 255f / 40f;同時(shí)根據(jù)半徑的大小設(shè)置透明度肯腕。

  • 繪制路徑
    首先要初始化一個(gè)路徑,來(lái)給PathMeasure測(cè)量钥平,就好像指定一條路一樣实撒,
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
        mInitPath.moveTo(mWidth / 2, mHeight / 2);
        mInitPath.lineTo(mWidth / 2 + 100, mHeight / 2 - 100);
        mInitPath.lineTo(mWidth / 2 + 300, mHeight / 2 - 100);
        pathAnimStartOrStop();
    }
      然后用PathMeasure中的方法進(jìn)行測(cè)量路徑的長(zhǎng)短,
      mPathMeasure = new PathMeasure(mInitPath, false);
      float length = mPathMeasure.getLength();

     將測(cè)量得到的值交給屬性動(dòng)畫(huà)涉瘾,需要判斷展開(kāi)還是知态,收縮
     ValueAnimator animator;
        if (flag) {
            animator = ValueAnimator.ofFloat(length, 0);
        } else
            animator = ValueAnimator.ofFloat(0, length);

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                animatedValue = (Float) animation.getAnimatedValue();
            }
        });

        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                //動(dòng)畫(huà)展開(kāi)式時(shí)候
                if (!flag) {
                    mPaint.setAlpha(255);
                    mPaint1.setAlpha(255);
                    startCircleAnim();
                    System.out.println("動(dòng)畫(huà)展開(kāi)的時(shí)候");
                }
                ReadBook1.this.setEnabled(false);
            }

            @Override
            public void onAnimationEnd(Animator animation) {

                if (flag) {
                    mPaintText.setAlpha(0);
                    mPaint.setAlpha(0);
                    mPaint1.setAlpha(0);
                    System.out.println("動(dòng)畫(huà)關(guān)閉的時(shí)候");
                    mAnimatorCircle.cancel();
                } else {
                    System.out.println("動(dòng)畫(huà)展開(kāi)的時(shí)候");
                    mPaintText.setAlpha(255);
                }
                flag = !flag;
                ReadBook1.this.setEnabled(true);
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });

        animator.setDuration(3000);
        animator.start();
        最后在onDraw方法中調(diào)用getSegment方法,得到實(shí)時(shí)路徑的長(zhǎng)短立叛,然后繪制
        mPath.reset();
        // 硬件加速的BUG负敏,需要設(shè)置到0,0
        mPath.lineTo(0, 0);
        float stop = animatedValue;
        float start = 0;
        mPathMeasure.getSegment(start, stop, mPath, true);
        canvas.drawPath(mPath, mPaint2);
        

這里面比較關(guān)鍵的地方就是float length = mPathMeasure.getLength();測(cè)量得到路徑的長(zhǎng)短,然后根據(jù)屬性動(dòng)畫(huà)動(dòng)態(tài)改變這個(gè)值秘蛇,最后調(diào)用 mPathMeasure.getSegment(start, stop, mPath, true);這個(gè)方法其做,得到實(shí)時(shí)的路徑長(zhǎng)短。
總結(jié):
1)需要判斷動(dòng)畫(huà)是展開(kāi)還是收縮赁还,根據(jù)這個(gè)設(shè)置屬性動(dòng)畫(huà)的值妖泄,
2)監(jiān)聽(tīng)動(dòng)畫(huà)的開(kāi)始和結(jié)束,根據(jù)這個(gè)顯示和隱藏中間的圓
3)動(dòng)畫(huà)展開(kāi)結(jié)束時(shí)顯示文字艘策。

最后不斷循環(huán)的動(dòng)畫(huà)會(huì)造成內(nèi)存泄露蹈胡,作為一個(gè)嚴(yán)謹(jǐn)?shù)某绦騿T千萬(wàn)不能忘記。

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mAnimatorCircle != null)
            mAnimatorCircle.cancel();
    }

全部代碼如下

public class ReadBook1 extends View {
    /**
     * 中心圓的畫(huà)筆
     */
    private Paint mPaint;
    /**
     * 漸變圓的畫(huà)筆
     */
    private Paint mPaint1;
    /**
     * 路徑的畫(huà)筆
     */
    private Paint mPaint2;
    /**
     * 文字的畫(huà)筆
     */
    private Paint mPaintText;
    /**
     * 初始化路徑柬焕,這個(gè)路徑?jīng)]有真正的繪制
     */
    private Path mInitPath;
    /**
     * 用getSegment獲取的路徑片段
     */
    private Path mPath;
    /**
     * 中心圓的半徑
     */
    private int mRadius = 10;
    /**
     * 圓漸變的動(dòng)畫(huà)
     */
    private ValueAnimator mAnimatorCircle;
    /**
     * 測(cè)量路徑的類
     */
    private PathMeasure mPathMeasure;

    private Float animatedValue = 0f;

    private int mWidth;
    private int mHeight;

    //判斷是展開(kāi)是回收
    private boolean flag;

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

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

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

    public ReadBook1(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(Color.WHITE);

        mPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint1.setStyle(Paint.Style.FILL);
        mPaint1.setColor(Color.WHITE);

        mPaint2 = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint2.setStyle(Paint.Style.STROKE);
        mPaint2.setColor(Color.WHITE);
        mPaint2.setStrokeWidth(4);


        mPaintText = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaintText.setStyle(Paint.Style.STROKE);
        mPaintText.setColor(Color.WHITE);
        mPaintText.setAlpha(0);
        mPaintText.setTextAlign(Paint.Align.CENTER);

        mInitPath = new Path();
        mPath = new Path();

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
        mInitPath.moveTo(mWidth / 2, mHeight / 2);
        mInitPath.lineTo(mWidth / 2 + 100, mHeight / 2 - 100);
        mInitPath.lineTo(mWidth / 2 + 300, mHeight / 2 - 100);
        pathAnimStartOrStop();
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(mWidth / 2, mHeight / 2, 10, mPaint1);
        canvas.drawCircle(mWidth / 2, mHeight / 2, mRadius, mPaint);
        mPath.reset();
        // 硬件加速的BUG审残,需要設(shè)置到0,0
        mPath.lineTo(0, 0);
        float stop = animatedValue;
        float start = 0;
        mPathMeasure.getSegment(start, stop, mPath, true);
        canvas.drawPath(mPath, mPaint2);
        canvas.drawText("Hello World", mWidth / 2 + 200, mHeight / 2 - 104, mPaintText);
    }
    public void startCircleAnim() {
        mAnimatorCircle = ValueAnimator.ofFloat(10, 50);
        final float per = 255f / 40;
        mAnimatorCircle.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float animatedValue = (float) animation.getAnimatedValue();
                mRadius = (int) animatedValue;
                int a = (int) ((40 - (animatedValue - 10)) * per);
                mPaint.setAlpha(a);
                invalidate();
            }
        });
        mAnimatorCircle.setRepeatMode(ValueAnimator.RESTART);
        mAnimatorCircle.setRepeatCount(ValueAnimator.INFINITE);
        mAnimatorCircle.setDuration(3000);
        mAnimatorCircle.start();
    }

    public void pathAnimStartOrStop() {
        mPathMeasure = new PathMeasure(mInitPath, false);
        float length = mPathMeasure.getLength();
        ValueAnimator animator;
        if (flag) {
            animator = ValueAnimator.ofFloat(length, 0);
        } else
            animator = ValueAnimator.ofFloat(0, length);

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                animatedValue = (Float) animation.getAnimatedValue();
            }
        });

        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                //動(dòng)畫(huà)展開(kāi)式時(shí)候
                if (!flag) {
                    mPaint.setAlpha(255);
                    mPaint1.setAlpha(255);
                    startCircleAnim();
                    System.out.println("動(dòng)畫(huà)展開(kāi)的時(shí)候");
                }
                ReadBook1.this.setEnabled(false);
            }

            @Override
            public void onAnimationEnd(Animator animation) {

                if (flag) {
                    mPaintText.setAlpha(0);
                    mPaint.setAlpha(0);
                    mPaint1.setAlpha(0);
                    System.out.println("動(dòng)畫(huà)關(guān)閉的時(shí)候");
                    mAnimatorCircle.cancel();
                } else {
                    System.out.println("動(dòng)畫(huà)展開(kāi)的時(shí)候");
                    mPaintText.setAlpha(255);
                }
                flag = !flag;
                ReadBook1.this.setEnabled(true);
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });

        animator.setDuration(3000);
        animator.start();
    }


    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mAnimatorCircle != null)
            mAnimatorCircle.cancel();
    }

}

3.實(shí)現(xiàn)的效果

myreadbook.gif
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市斑举,隨后出現(xiàn)的幾起案子搅轿,更是在濱河造成了極大的恐慌,老刑警劉巖富玷,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件璧坟,死亡現(xiàn)場(chǎng)離奇詭異既穆,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)雀鹃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門幻工,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人黎茎,你說(shuō)我怎么就攤上這事囊颅。” “怎么了傅瞻?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵踢代,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我嗅骄,道長(zhǎng)胳挎,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任溺森,我火速辦了婚禮慕爬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘屏积。我一直安慰自己医窿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布肾请。 她就那樣靜靜地躺著留搔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪铛铁。 梳的紋絲不亂的頭發(fā)上隔显,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音饵逐,去河邊找鬼括眠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛倍权,可吹牛的內(nèi)容都是我干的掷豺。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼薄声,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼当船!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起默辨,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤德频,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后缩幸,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體壹置,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡竞思,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了钞护。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盖喷。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖难咕,靈堂內(nèi)的尸體忽然破棺而出课梳,到底是詐尸還是另有隱情,我是刑警寧澤余佃,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布惦界,位于F島的核電站,受9級(jí)特大地震影響咙冗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜漂彤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一雾消、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧挫望,春花似錦立润、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蛉幸,卻和暖如春破讨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背奕纫。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工提陶, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人匹层。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓隙笆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親升筏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子撑柔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,099評(píng)論 25 707
  • 光棍節(jié)快到了,提前祝愿廣大的單身猿猴您访,早日脫單铅忿,盡快找到另一半。 一直覺(jué)得 QQ 的小紅點(diǎn)非常具有創(chuàng)新洋只,新穎辆沦。要是...
    文淑閱讀 1,031評(píng)論 4 11
  • 轉(zhuǎn)載文章作者:一息尚存 原文鏈接:http://www.reibang.com/p/349aa6153fcc 效...
    木木00閱讀 980評(píng)論 1 20
  • 你問(wèn)我甜不甜呀甜不甜 我問(wèn)你咸不咸呀咸不咸 難得無(wú)事兩個(gè)人兩只碗 對(duì)坐吃飯談?wù)勌?我問(wèn)你咸不咸呀咸不咸 你問(wèn)我甜不...
    黃梁一閱讀 183評(píng)論 0 0
  • 鏡花山昼捍,蒼翠青郁的皮膚下埋藏著一顆褶皺干癟的靈魂。 目的地在哪里肢扯?陳靜并不清楚妒茬,她只是渾渾噩噩地買車票,不斷前行蔚晨,...
    古來(lái)古來(lái)閱讀 571評(píng)論 2 8