從無(wú)到有打造一個(gè)炫酷的進(jìn)度條效果

SpecialProgressBar

今天這篇文章要介紹的是一個(gè)酷炫的進(jìn)度條的設(shè)計(jì)和實(shí)現(xiàn),在進(jìn)度的文字內(nèi)容疮绷、顏色以及切換的圖片等都可以自由設(shè)置。我們先看下效果 (創(chuàng)意受Dribbble的啟發(fā)):

SpecialProgressBar
SpecialProgressBar

整體效果還是不錯(cuò)的吧,哈哈,我自己還是比較滿意的~項(xiàng)目地址已上傳至 github 口猜,歡迎star、fork透揣。那么下面我們就開(kāi)始從無(wú)到有實(shí)現(xiàn)一下這個(gè)酷炫的進(jìn)度效果吧济炎。
項(xiàng)目地址SpecialProgressBar

實(shí)現(xiàn)思路

仔細(xì)觀察下這個(gè)效果辐真,它有不同的動(dòng)態(tài)效果和不同的進(jìn)度狀態(tài)組成须尚,那么實(shí)現(xiàn)的思路就用效果切換的不同狀態(tài)來(lái)進(jìn)行切換繪制崖堤,對(duì)應(yīng)數(shù)值的變化用到值動(dòng)畫(huà)、Path耐床、貝塞爾曲線密幔、Camera與Matrix等相關(guān)工具,因?yàn)樯婕暗牡胤奖容^多撩轰,這里我就主要說(shuō)下大體的實(shí)現(xiàn)思路以及相關(guān)注意點(diǎn)胯甩。

主要分五點(diǎn)來(lái)進(jìn)行分析:

一、利用值動(dòng)畫(huà)變換數(shù)值然后invalidate刷新界面钧敞,形成動(dòng)畫(huà)效果蜡豹。
二麸粮、在不同的臨界值切換不同的狀態(tài)溉苛。
三、利用PathMeasure與Path來(lái)實(shí)現(xiàn)進(jìn)度效果弄诲。
四愚战、利用阻尼動(dòng)畫(huà)實(shí)現(xiàn)進(jìn)度條回彈效果。
五齐遵、利用Camera和Matrix(如果不了解可以看我上一篇文章:Android中利用Camera與Matrix實(shí)現(xiàn)3D效果詳解)實(shí)現(xiàn)進(jìn)度框翻轉(zhuǎn)效果寂玲。

一、利用值動(dòng)畫(huà)變換數(shù)值然后invalidate刷新界面梗摇,形成動(dòng)畫(huà)效果

對(duì)位置拓哟、大小、顏色的切換主要使用的ValueAnimator來(lái)進(jìn)行變化伶授,看下代碼片段:

 ValueAnimator va = ValueAnimator.ofInt((int)(Math.min(getWidth(), getHeight())- mBgPaint.getStrokeWidth()*2)/2,(int) mBgPaint.getStrokeWidth());
            va.setInterpolator(new AnticipateInterpolator());
            va.setDuration(800);
            va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    int value = (Integer) animation.getAnimatedValue();
                    radiu = value;
                    center_scaleX = (1 - animation.getAnimatedFraction());
                    center_scaleY = (1 - animation.getAnimatedFraction());
                    invalidate();
                }
            });
            va.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {
                }
                @Override
                public void onAnimationEnd(Animator animation) {
                    state = STATE_READY_CHANGEING;//準(zhǔn)備階段
                    mBgPaint.setStyle(Paint.Style.STROKE);
                    mBgPaint.setColor(Color.BLACK);
                    changeStateReadyChanging();
                }
                @Override
                public void onAnimationCancel(Animator animation) {
                }
                @Override
                public void onAnimationRepeat(Animator animation) {
                }
            });
            va.start();

通過(guò)不同Interpolator插值器實(shí)現(xiàn)不同的運(yùn)動(dòng)效果断序,在AnimatorUpdateListener中改變數(shù)值,然后調(diào)用invalidat()方法糜烹,之后onDraw()方法會(huì)被調(diào)用违诗,我們改變的數(shù)值在界面是就可以看到產(chǎn)生的動(dòng)態(tài)效果了。

二疮蹦、在不同的臨界值切換不同的狀態(tài)

考慮到動(dòng)畫(huà)中涉及的動(dòng)畫(huà)效果還是比較多的诸迟,可以用不同的狀態(tài)表示不同的動(dòng)畫(huà)區(qū)間,在動(dòng)畫(huà)結(jié)束時(shí)切換不同的狀態(tài)愕乎,然后在invalidate方法中根據(jù)不同的狀態(tài)進(jìn)行繪制阵苇,我們來(lái)看下吧:

    private static final int STATE_READY = 0;
    private static final int STATE_READY_CHANGEING = 1;
    private static final int STATE_READYING = 2;
    private static final int STATE_ERROR = 3;
    private static final int STATE_STARTING = 4;
    private static final int STATE_SUCCESS = 5;
    private static final int STATE_BACK = 6;
    private static final int STATE_BACK_HOME = 7;
    private static final int DONE = 8;

這里定義了九種狀態(tài),代表不同的動(dòng)畫(huà)效果區(qū)間感论,根據(jù)這些狀態(tài)來(lái)進(jìn)行動(dòng)態(tài)切換繪制:

switch (state) {
            case STATE_BACK_HOME:
            case STATE_READY:
                p.reset();
                mBgPaint.setStyle(Paint.Style.FILL);

                p.addCircle(getWidth() / 2, getHeight() / 2, radiu, Path.Direction.CCW);
                canvas.drawPath(p, mBgPaint);
                matrix.reset();
                matrix.setScale(center_scaleX, center_scaleY);
                matrix.preTranslate(0,0);
                matrix.postTranslate(getWidth() / 2 - downloadBitmap.getWidth() / 2*Math.max(center_scaleX,center_scaleY), getHeight() / 2 - downloadBitmap.getHeight() / 2*Math.max(center_scaleX,center_scaleY));

                canvas.drawBitmap(downloadBitmap, matrix, mBgPaint);
                break;
            case STATE_READY_CHANGEING:
                p.reset();
                p.moveTo(startX, startY);
                p.lineTo(endX, endY);
                canvas.drawPath(p, mBgPaint);
                break;
                
                ...
          }

這里的狀態(tài)切換可以說(shuō)是整個(gè)動(dòng)畫(huà)切換的核心慎玖,通過(guò)對(duì)不同狀態(tài)的切換,然后對(duì)應(yīng)切換不同的值動(dòng)畫(huà)笛粘,實(shí)現(xiàn)整個(gè)效果的動(dòng)態(tài)銜接趁怔。

三湿硝、利用PathMeasure與Path來(lái)實(shí)現(xiàn)進(jìn)度效果

PathMeasure作為一個(gè)輔助工具,在對(duì)Path路徑進(jìn)行處理時(shí)是很方便的润努,我們來(lái)看下它吧:

setPath(Path path, boolean forceClosed)關(guān)聯(lián)一個(gè)Path
isClosed() 是否閉合
getLength() 獲取Path的長(zhǎng)度
nextContour() 跳轉(zhuǎn)到下一個(gè)輪廓
getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)截取路徑片段
getPosTan(float distance, float[] pos, float[] tan)獲取指定長(zhǎng)度的位置坐標(biāo)及該點(diǎn)切線值
getMatrix(float distance, Matrix matrix, int flags)獲取指定長(zhǎng)度的位置坐標(biāo)及該點(diǎn)Matrix

我們這里重點(diǎn)關(guān)注getSegment和getPosTan方法关斜,

getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo),相關(guān)參數(shù):startD 開(kāi)始截取位置距離 Path 起點(diǎn)的長(zhǎng)度铺浇,stopD 結(jié)束截取位置距離 Path 起點(diǎn)的長(zhǎng)度痢畜,dst 截取的 Path 將會(huì)添加到 dst 中,startWithMoveTo 起始點(diǎn)是否使用 moveTo鳍侣。

在進(jìn)度條變化的時(shí)候我們就使用這個(gè)方法動(dòng)態(tài)的截取從開(kāi)始位置到當(dāng)前位置的Path值丁稀,截取成功dst中就用截取路徑的值,然后調(diào)用drawPath方法繪制出進(jìn)度效果倚聚。

需要注意的是:在安卓4.4或者之前的版本线衫,在默認(rèn)開(kāi)啟硬件加速的情況下,更改 dst 的內(nèi)容后可能繪制會(huì)出現(xiàn)問(wèn)題惑折,請(qǐng)關(guān)閉硬件加速或者給 dst 添加一個(gè)單個(gè)操作授账,例如: dst.rLineTo(0, 0)。

getPosTan (float distance, float[] pos, float[] tan)
相關(guān)參數(shù):
distance 距離 Path 起點(diǎn)的長(zhǎng)度
pos 該點(diǎn)的坐標(biāo)值
tan 該點(diǎn)的正切值

這里我們用這個(gè)方法來(lái)獲取當(dāng)前點(diǎn)的坐標(biāo)值惨驶,如果獲取成功白热,pos中就有坐標(biāo)值了,通過(guò)這個(gè)坐標(biāo)值來(lái)動(dòng)態(tài)改變進(jìn)度框和進(jìn)度文字的位置粗卜。

四屋确、利用阻尼動(dòng)畫(huà)實(shí)現(xiàn)進(jìn)度條回彈效果

開(kāi)始進(jìn)度前進(jìn)度條有個(gè)回彈的效果,這里我們使用的阻尼效果续扔,主要用設(shè)置插值器動(dòng)態(tài)改變二階貝塞爾曲線的定點(diǎn)攻臀,定點(diǎn)位置的改變,形成整個(gè)路徑效果的改變测砂。
阻尼插值器參考網(wǎng)上的實(shí)現(xiàn)茵烈,我們看下主要實(shí)現(xiàn):

public DampingInterpolator(int count, float overshoot) {
        setOverShootCount(count);
        setOverShootPercent(overshoot);
    }

    public void setOverShootCount(int count) {
        mCount = Math.max(1, count);
        mRegion = (float) (Math.PI * 2 * (mCount - 1) + Math.PI / 2 * 3);
        mOvershootModulus = (float) Math.pow(mOvershootPercent, mRegion
                / Math.PI);
    }

    public void setOverShootPercent(float overshoot) {
        mOvershootPercent = Math.max(0, Math.min(1, overshoot));
       /*
        * 當(dāng) t * mRegion = Math.PI 的時(shí)候,達(dá)到第一次過(guò)沖的峰值砌些, 則 t = Math.PI / mRegion 呜投。 且此時(shí)
        * mOvershootModulus^t = mOvershootPercent , 所以 mOvershootModulus =
        * Math.pow(mOvershootPercent, 1 / t) 存璃, 即 mOvershootModulus =
        * Math.pow(mOvershootPercent, mRegion / Math.PI) 仑荐。
        */
        mOvershootModulus = (float) Math.pow(mOvershootPercent, mRegion
                / Math.PI);
    }
     @Override
    public float getInterpolation(float t) {
        if (t <= 0) {
            return 0;
        }
        if (t >= 1) {
            return 1;
        }
        return (float) (1 - Math.pow(mOvershootModulus, t)
                * Math.cos(mRegion * t));
    }

將阻尼插值器設(shè)置給我們要開(kāi)啟的值動(dòng)畫(huà),改變二階貝塞爾曲線的定點(diǎn)纵东,定點(diǎn)的來(lái)回回彈粘招,最終形成曲線的來(lái)回回彈。

五偎球、利用Camera和Matrix實(shí)現(xiàn)進(jìn)度框翻轉(zhuǎn)效果

在失敗和成功時(shí)洒扎,進(jìn)度框有個(gè)沿X軸和沿Y軸旋轉(zhuǎn)的效果辑甜,如果這里單單使用matrix不能實(shí)現(xiàn)效果,僅僅是在平面沿Z軸旋轉(zhuǎn)的袍冷。為了實(shí)現(xiàn)整個(gè)效果磷醋,我們使用Camera和Matrix來(lái)進(jìn)行實(shí)現(xiàn),調(diào)用camera的roateX和roateY方法進(jìn)行旋轉(zhuǎn)胡诗,具體可以看我上篇文章:Android中利用Camera與Matrix實(shí)現(xiàn)3D效果詳解邓线。

看下具體代碼:

                camera.save();
                camera.rotateY(rotateY);
                camera.getMatrix(cameraMatrix);
                camera.restore();

                cameraMatrix.preTranslate(0, -loadingBitmap.getHeight() / 2);
                cameraMatrix.postTranslate(POS[0], POS[1] - loadingBitmap.getHeight() / 2);
                canvas.drawBitmap(loadingBitmap, cameraMatrix, mBgPaint);

我們?cè)谡{(diào)用rotateY方法后獲取到Matrix,然后調(diào)用canvas的drawBitmap方法來(lái)動(dòng)態(tài)改變進(jìn)度框的位置和文字的位置煌恢,一個(gè)動(dòng)態(tài)效果就出來(lái)啦~

這里主要把主要的難點(diǎn)和主題思路縷了一下骇陈,如果要關(guān)注具體細(xì)節(jié),可查看源碼瑰抵。
github地址:https://github.com/zhangke3016/SpecialProgressBar】如果喜歡你雌,歡迎star、fork谍憔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末匪蝙,一起剝皮案震驚了整個(gè)濱河市主籍,隨后出現(xiàn)的幾起案子习贫,更是在濱河造成了極大的恐慌,老刑警劉巖千元,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件苫昌,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡幸海,警方通過(guò)查閱死者的電腦和手機(jī)祟身,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)物独,“玉大人袜硫,你說(shuō)我怎么就攤上這事〉猜ǎ” “怎么了婉陷?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)官研。 經(jīng)常有香客問(wèn)我秽澳,道長(zhǎng),這世上最難降的妖魔是什么戏羽? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任担神,我火速辦了婚禮,結(jié)果婚禮上始花,老公的妹妹穿的比我還像新娘妄讯。我一直安慰自己孩锡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布亥贸。 她就那樣靜靜地躺著浮创,像睡著了一般。 火紅的嫁衣襯著肌膚如雪砌函。 梳的紋絲不亂的頭發(fā)上斩披,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音讹俊,去河邊找鬼垦沉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛仍劈,可吹牛的內(nèi)容都是我干的厕倍。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼贩疙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼讹弯!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起这溅,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤组民,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后悲靴,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體臭胜,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年癞尚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了耸三。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡浇揩,死狀恐怖仪壮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情胳徽,我是刑警寧澤积锅,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站膜廊,受9級(jí)特大地震影響乏沸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜爪瓜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一蹬跃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦蝶缀、人聲如沸丹喻。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)碍论。三九已至,卻和暖如春柄慰,著一層夾襖步出監(jiān)牢的瞬間鳍悠,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工坐搔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留藏研,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓概行,卻偏偏與公主長(zhǎng)得像蠢挡,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子凳忙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,162評(píng)論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)业踏、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,105評(píng)論 4 62
  • 直播營(yíng)銷(xiāo)是從視頻營(yíng)銷(xiāo)發(fā)展起來(lái)的涧卵,視頻營(yíng)銷(xiāo)以其圖文并茂的特點(diǎn)勤家,使廣告更生動(dòng),更能吸引讀者眼球艺演,這是商家非常喜歡的一種...
    鐵牛哥閱讀 4,076評(píng)論 0 1
  • 望離眷閱讀 133評(píng)論 0 0
  • 在上一篇文章中我們談到了此次智能革命對(duì)幾乎各個(gè)行業(yè)的影響却紧,那么為什么這一次的技術(shù)革命會(huì)有如此巨大的影響呢桐臊? 如果讓...
    不知所然并卵閱讀 236評(píng)論 0 2