自定義view實現超萌動感小炸彈

weixin_33866037發表於2017-10-27

宣告:本文章獨家授權微信公眾號碼個蛋原創推文

Hello,小夥伴們,我回來了。這些日子有的小夥伴問我怎麼沒有更新了。這個其實是有原因,首先,最近有點忙。其次沒有看到什麼覺得好玩的動畫!最後,就是我更新過了!!ThreadLocal原始碼完全解析,只是你們原始碼不感冒,然後你們忽略了!!!!忽略了!!!還有,我其實有更新一個薄荷捲尺,只是覺得有點簡單,而且還像也有什麼好講的,所以只是上傳到github,沒有文章。
客套話已經完了,現在開始我們的超萌動感小炸彈之旅
首先,我還是先感謝一下作者,設計出這麼棒的動畫!!設計出處點我
效果如下,Amazing:

837800-b2f27ae104cbf83a.gif
preview.gif

再來看android的實現效果。

837800-3301776994029a39.gif
android實現

下面我們和自定義view實現超萌動感天氣小太陽一樣,開始解析動畫!(沒看過天氣小太陽的朋友可以先去看天氣小太陽,有些天氣小太陽講過的套路將不再講,同時需要掌握pathcamera、貝塞爾曲線等,不然部分程式碼可能會引起不適)。

我們先把靜態view繪製出來,然後再實現動畫,Let's go。

837800-81b6f31cc99bbee7.png
靜態效果
1.地板
837800-8126fe55f6024857.png
image.png

可以看到地板其實就是一條直線。然後中間兩個缺口。這要個麼實現呢?看到小太陽的小夥伴可能都會說,這很簡單。只要畫一線直線然後覆蓋兩個白的區間就可以了。的確這可以實現,但是仔細觀察可以發現下方的缺口是兩個半圓加矩形實現的,這樣的話就有點麻煩,而且不方便缺口位置的移動。那有什麼簡單的方法呢?有,那就是使用Path進行繪畫一條直線,然後通過設定圓筆頭,再設定DashPathEffect(實現虛線,一段畫,一段不畫的效果,可以自由控制各段長度)來實現間隔(本view的缺口都是使用此特性實現的,不熟悉的小夥伴可以去看一下),程式碼如下:

        float[] groundEffectFloat=new float[]  {bombLineWidth/4,bombLineWidth/2+bombLineWidth,bombLineWidth*2,bombLineWidth/3*2+bombLineWidth,getMeasuredWidth(),0};//設定畫與不畫所佔長度
        groundDashPathEffect=new DashPathEffect(groundEffectFloat,0);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(bombLineColor);
        mPaint.setPathEffect(groundDashPathEffect);//設定虛線效果
        mPath.reset();
        mPath.moveTo(bombLineWidth/2,getMeasuredHeight()-bombLineWidth/2);
        mPath.lineTo(getMeasuredWidth()-bombLineWidth/2,getMeasuredHeight()-  
        bombLineWidth/2);
        canvas.drawPath(mPath,mPaint);
2.身體的邊框

837800-44d4bd32285d1145.png
image.png

仔細一看!聰明的你一定會說太簡單了,這不就是一個圓然後再用DashPathEffect實現缺口不就可以了!!嗯,對,就是這樣的。直接放程式碼:

        mPaint.setPathEffect(bodyDashPathEffect);
        mPaint.setColor(bombLineColor);
        mPaint.setStyle(Paint.Style.STROKE);
        mPath.reset();
        mPath.addCircle(bombCenterX,bombCenterY,bodyRadius,   
        Path.Direction.CW);
        canvas.drawPath(mPath,mPaint);
        canvas.restore();

簡單!簡單的不能再簡單了,下面看身體

3.身體
837800-939a3d1f8e439854.png
image.png

可以發現身體其實也就是一個圓,然後加上左上角的高光。那麼高光是怎麼實現的呢?
三個點的高光,很簡單的,用Path畫弧,然後使用DashPathEffect效果,完美。
那麼另一個高光呢?看圖。

837800-109ce87c586fb991.png
image.png

可以看到就是條圓弧和一個路徑合成的,然後裁剪保持圓內。路徑的形成就是取弧度的兩個點,然後用貝塞爾曲線進行繪製,控制點位於弧度中分線中(下圖紅點)

837800-33634b4d2745a591.png
image.png
837800-fa7bc01047ae6631.png
image.png

程式碼如下:(部分程式碼,左上角高光的,其它的請檢視原始碼)

       //左上角的光邊
        mPaint.setPathEffect(null);
        mRectF.set(bombCenterX-bodyRadius+bombLineWidth/2,bombCenterY-bodyRadius+bombLineWidth/2
        ,bombCenterX+bodyRadius-bombLineWidth/2,getMeasuredHeight()-bombLineWidth-bombLineWidth/2);
        canvas.drawArc(mRectF,160,100,false,mPaint);
        //拼接光邊
        mPath.reset();
        mPath.addCircle(bombCenterX,bombCenterY,bodyRadius-bombLineWidth/2, Path.Direction.CCW);
        canvas.save();
        canvas.clipPath(mPath);//裁剪圓內

        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(lightColor);
        canvas.drawPath(mBodyLightPath,mPaint);
        canvas.restore();
4.臉
837800-f3c2697c9be9cf4e.png
image.png

大家可以看到,好你有點複雜的,其實還好。這裡是因為使用了Z軸旋轉,看起來有點複雜,那我們移到中間。

837800-a659c1588dfb9044.png
image.png

好像簡單了,眼睛和酒窩簡單,4個圓!!嘴巴,這個。。。這個好像有點噁心啊。其實不然,看圖。

837800-e25dc1667a5393f7.png
image.png
837800-e0775d74e1182a57.png
image.png

其實就是一個圓然後再加上一個路徑圖就可以實現,紅點表示的是控制點。空心點表示節點。細心的朋友可能發現,不對啊。舌頭下面不全是紅的,和嘴巴是分開的。這裡只需要把嘴巴按比例縮小,然後和嘴巴做個Xfermode就可以了。部分程式碼:

        //畫舌頭 圓和嘴巴的縮放相交,mpath是嘴巴的路徑
        int save=canvas.saveLayer(0,0,getMeasuredWidth(),getMeasuredHeight(),null,Canvas.ALL_SAVE_FLAG);
        canvas.drawPath(mPath,mPaint);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        mPaint.setColor(Color.parseColor("#f34671"));
        canvas.drawCircle(bombCenterX,mouthY+(mouthMaxY-mouthY)/8+bodyRadius/(5-1.4f*mouthOffsetPercent),bodyRadius/5,mPaint);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        canvas.scale(0.8f,0.8f,bombCenterX,(mouthMaxY+mouthY)/2);
        canvas.drawPath(mPath,mPaint);
        canvas.restoreToCount(save);
        mPaint.setXfermode(null);
5.臉上的陰影(不知道叫什麼,暫時稱為陰影遮罩)
837800-3a1448ecae2fea06.png
image.png

一看,個別好事的小夥伴說,你不會又讓我用貝塞爾曲線畫吧!這個不好找啊!!冷靜冷靜,這個實現如下:

837800-77191822ed806f18.png
image.png

如此簡單,兩個圓取紅圓未相交的部分。

//兩個圓相交產生陰影
        int save=canvas.saveLayer(0,0,getMeasuredWidth(),getMeasuredHeight(),null,Canvas.ALL_SAVE_FLAG);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(bombShadowColor);
        canvas.drawCircle(bombCenterX,bombCenterY,bodyRadius-bombLineWidth/2,mPaint);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
        canvas.translate(-bodyRadius/5,-bodyRadius/5);
        mPaint.setColor(bombColor);
        canvas.drawCircle(bombCenterX,bombCenterY,bodyRadius-bombLineWidth/2,mPaint);
        canvas.restoreToCount(save);
        mPaint.setXfermode(null);
6.頭
837800-5e459bb3dff1872c.png
image.png

小夥伴又要說了。這個不好畫,不好畫!!冷靜冷靜。這個其實更簡單。只要把頭放在身體的後面一層就可以了。看圖:

837800-35288c6c208d1580.png
image.png

程式碼:

太簡單,我不想貼了,假裝我是程式碼
7.引線
837800-98e75e6ec8b96d29.png
image.png

這個引線,其實也就是一線曲線,貝塞爾曲線繼續上場(不解釋,不懂的請面壁去)。

837800-04d3d29940e76e9a.png
image.png
8.爆炸效果
837800-ef01291fd94f983c.png
image.png

簡單的不太再簡單了,4個圓,半徑從大到小畫,中間然後挖空。so easy!!

        int save = canvas.saveLayer(0,0,getMeasuredWidth(),getMeasuredHeight(),null,Canvas.ALL_SAVE_FLAG);
        float distance = maxBlastCircleRadius/12;
        //畫圓
        mPaint.setColor(lightColor);
        canvas.drawCircle(bombCenterX,circleY, currentBlastCircleRadius,mPaint);
        mPaint.setColor(bombColor);
        canvas.drawCircle(bombCenterX,circleY, currentBlastCircleRadius -distance,mPaint);
        mPaint.setColor(bombLineColor);
        canvas.drawCircle(bombCenterX,circleY, currentBlastCircleRadius -distance*2,mPaint);
        mPaint.setColor(lightColor);
        canvas.drawCircle(bombCenterX,circleY, currentBlastCircleRadius -distance*3,mPaint);
        //掏空
        if (blastCircleRadiusPercent >0.65) {
            mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
            canvas.drawCircle(bombCenterX, circleY, currentBlastCircleRadius - maxBlastCircleRadius * 0.65f, mPaint);
            mPaint.setXfermode(null);
        }
        canvas.restoreToCount(save);

到這裡,我們已經完成了一半,那就是小炸彈的顯示,現在到了動畫的時間了!再次出場


837800-3301776994029a39.gif
android實現
9.臉左右移動動畫

可以看到左右移動,在移動的時間然後我們只需要在畫臉的時間加一個偏移,然後在移動的過程中,會發現臉會繞炸彈身體的中心旋轉。所以程式碼如下

        canvas.save();
        mCamera.save();
        mCamera.rotate(bombTBRotate,0,-bombLRRotate/3);
        mMatrix.reset();
        mCamera.getMatrix(mMatrix);
        mCamera.restore();
        mMatrix.preTranslate(-bombCenterX,-(bombCenterY));
        mMatrix.postTranslate(bombCenterX,bombCenterY);
        mMatrix.postTranslate(faceLROffset,faceTBOffset);
        canvas.setMatrix(mMatrix);

使用camera,進行z軸的旋轉,然後再進行translate左右移動,然後使用valueanimator動畫對變偏移進行設定,搞定!在移動過程中,可以發現眼睛有眯下的效果。這個很簡單,可以把眼睛用橢圓來實現,保持寬度不變,改變高度就可以了。

837800-5f5af33d54b7e85c.png
image.png
10.身體頭部引線左右旋轉

這個就更簡單了,只需要在畫之前用camera旋轉變換獲取martix,然後對canvas進行變換。

11.臉部上下移動

首先和臉部左右移動一樣,使用matrix.translate進行上下移動。眼睛的變換也一樣。後面的眼睛放大效果,就是在變成圓的眼睛的時候,放大圓的半徑。
嘴巴的變換就相對比較複雜!看圖,高能預警,我也不知道我講不講得清楚!!!!

837800-63f544df013b0b1e.png
image.png

這是剛才畫嘴巴的圖!!!嘴巴動畫有兩個部分!!(以下語句可能會引起不適)

  • 第一部分嘴角往兩邊移動,嘴巴變扁。這裡我們需要把ab兩點用屬性動畫往兩邊移動(兩邊的拐角點同樣移動),c點往上方移動,然後回到原始位置。
  • 第二部分是ab兩點往中間靠攏,直到ab重合,同時ab兩點往上移,de的控制點同時拉長,直到形成一個橢圓。

不理解!!不理解再好好想象一下,空間想象能力。要是還想不懂,那就算了。畢竟平時用得不多。

12.炸彈引線,點燃效果

炸彈引線效果同樣分兩個部分

  • 一個是引線變短,可以根據PathMeasure,獲取Path的比例Path(比如70%Path),這樣我們就可以通過ValueAnimator用一個01的比例來繪製引線變短的效果
      //mHeadLinePath是引線的完整Path
       mPathMeasure.setPath(mHeadLinePath,false);
        mPath.reset();
      mPathMeasure.getSegment(0,mPathMeasure.getLength()*headLinePercent,mPath,true);//根據比例獲取對應比例的引線
        canvas.drawPath(mPath,mPaint);
  • 第二部分是點燃的效果。其實就是一個金色的實心圓,然後一個紅色的圓邊框,中間白色,三個圓按不同的速率和極限做放大縮小動畫 (這裡原設計還加入了變色的功能,金色圓會變色,可以用ArgbEvaluator實現)。
837800-b69c4c5ab2e5e9c3.png
image.png
13.爆炸動畫

和引線動畫型別,4個圓做放大縮小動畫,只是到一定的大小後,然後圓小漏空,並且漏空逐漸放大。

14.結語

好了,我們的超萌動感小炸彈到這裡就結束了。希望小夥伴們能有所收穫,掌握更多自定義view的套路,更多分析方法,我們下次見。

原始碼點我

相關文章