自定義view實(shí)現(xiàn)超萌動(dòng)感天氣小太陽(yáng)

前些天,在IXUS上看到一個(gè)很贊的動(dòng)畫(huà),一下子就看對(duì)眼了,于是便決定用Android來(lái)實(shí)現(xiàn)一下,效果如下:

原圖

設(shè)計(jì)在這里:Jana,點(diǎn)贊超棒的設(shè)計(jì)師

Android實(shí)現(xiàn)的效果如下:

android實(shí)現(xiàn)

現(xiàn)在開(kāi)始分析如何實(shí)現(xiàn)這個(gè)動(dòng)畫(huà):

image.png

當(dāng)我們看到一個(gè)動(dòng)畫(huà)要實(shí)現(xiàn)的時(shí)候,很多朋友可能就直接照著開(kāi)始實(shí)現(xiàn)了,其實(shí)這樣是很難實(shí)現(xiàn)。我們?cè)诮佑|到一個(gè)新動(dòng)畫(huà)的時(shí)候,首先要對(duì)動(dòng)畫(huà)進(jìn)行分解。例如本動(dòng)畫(huà),我們進(jìn)行分析后,可以把動(dòng)畫(huà)分為以下幾個(gè)部分:

1. 圓環(huán)放大縮小消失效果:
圓環(huán)放大縮小效果

進(jìn)行剖析后,我們可以發(fā)現(xiàn)這是一個(gè)圓環(huán)放大縮小的動(dòng)畫(huà),有3個(gè)關(guān)鍵幀

關(guān)鍵幀1
關(guān)鍵幀2
關(guān)鍵幀3

從關(guān)鍵幀1->2->3->圓環(huán)消失,那對(duì)我們來(lái)說(shuō)就是使用屬性動(dòng)畫(huà)進(jìn)行繪制圓環(huán),我是通過(guò)繪制兩個(gè)圓形成圓環(huán)(黃色大圓在下面,白色小圓在上面)的效果。代碼如下

private void drawZoomRing(Canvas canvas) {
        mPaint.setShader(null);
        mPaint.setStrokeWidth(0);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(ringColor);
        canvas.drawCircle(getMeasuredWidth()/2,getMeasuredHeight()/2,ringWidth/2,mPaint);//外圓大圓
        mPaint.setColor(Color.WHITE);
        canvas.drawCircle(getMeasuredWidth()/2,getMeasuredHeight()/2,minRingCenterWidth/2,mPaint);//肉圓小圓
    }

這里是通過(guò)控制大圓和小圓的半徑大小,從而來(lái)控制圓環(huán)的位置和大小。然后只需要通過(guò)ValueAnimator動(dòng)態(tài)改變ringWidthminRingCenterWidth的值然后invalidate(),即可以實(shí)現(xiàn)第一部分的圓環(huán)放大縮小效果。

2. 圓弧轉(zhuǎn)動(dòng)縮小動(dòng)畫(huà)
圓弧轉(zhuǎn)動(dòng)縮小動(dòng)畫(huà)

這里其實(shí)就是處于不同圓下的兩條圓弧,在做旋轉(zhuǎn)和長(zhǎng)度變化動(dòng)畫(huà)
我們先來(lái)看一下圓弧的方法:

canvas.drawArc(圓弧所在的圓的正方形框,開(kāi)始角度,跨越角度,是否要連接圓心,mPaint);

那么我們就可以通過(guò)動(dòng)態(tài)改變開(kāi)始角度和跨越角度來(lái)實(shí)現(xiàn)圓弧的移動(dòng)和長(zhǎng)度變化,這里的圓弧也是有3個(gè)關(guān)鍵幀,圓弧長(zhǎng)度初始狀態(tài)(外弧90度,內(nèi)弧2度)->圓弧長(zhǎng)度達(dá)到180度->0度消失

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

private void drawArcLine(Canvas canvas) {
        mPaint.setColor(ringColor);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeCap(Paint.Cap.ROUND);//線的兩邊圓頭模式
        mPaint.setStrokeWidth(getMeasuredWidth()/40);//通過(guò)畫(huà)筆的寬度來(lái)控制圓弧的寬度
        canvas.drawArc(mRectCenterArc, centerArcEndAngle-centerArcAngle,centerArcAngle,false,mPaint);//畫(huà)內(nèi)弧
        mPaint.setStrokeWidth(getMeasuredWidth()/25);
        canvas.drawArc(mRectOutSideArc,outSideArcStartAngle,outSideArcAngle,false,mPaint);//畫(huà)外弧
    }

細(xì)心的小伙伴可能已經(jīng)發(fā)現(xiàn)了,畫(huà)外弧和內(nèi)弧的參數(shù)還像有點(diǎn)不太一樣。因?yàn)閮?nèi)弧是順時(shí)針旋轉(zhuǎn),外弧是逆時(shí)針旋轉(zhuǎn)的,為了保證弧轉(zhuǎn)動(dòng)到某一點(diǎn)后不再移動(dòng),我們做了處理,內(nèi)弧以逆向的思維來(lái)做。(可以編寫(xiě)代碼試下這么做有什么好處)

3. 太陽(yáng)出現(xiàn)以及旋轉(zhuǎn)
太陽(yáng)出現(xiàn)以及旋轉(zhuǎn)

我們先分析一下太陽(yáng)出現(xiàn)以及旋轉(zhuǎn)動(dòng)畫(huà)下太陽(yáng)的組成成分,可以發(fā)現(xiàn)太陽(yáng)由以下二部分組成:兩個(gè)正方形和一個(gè)圓形

image.png

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

private void drawSun(Canvas canvas) {
        mPaint.setStrokeWidth(0);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(ringColor);
        mPaint.setShader(mFlowerLinearGradient);//設(shè)置顏色漸變
        canvas.save();
        canvas.rotate(sunRotateAngle,getMeasuredWidth()/2,getMeasuredHeight()/2);
        canvas.drawRect(mRectFSunFlower,mPaint);
        canvas.rotate(45,getMeasuredWidth()/2,getMeasuredHeight()/2);//第二個(gè)正方形比第一個(gè)正方形多旋轉(zhuǎn)45度
        mPaint.setShader(mFlowerRotateLinearGradient);
        canvas.drawRect(mRectFSunFlower,mPaint);
        canvas.restore();
        mPaint.setShader(null);
        canvas.drawCircle(getMeasuredWidth()/2,getMeasuredHeight()/2,sunWidth/2,mPaint);//畫(huà)圓
    }

這里我們可以先畫(huà)第一個(gè)綠色的正方形,然后畫(huà)第二個(gè)紅色的正文形,這里通過(guò)一個(gè)變量sunRotateAngle來(lái)控制兩個(gè)正方形的旋轉(zhuǎn),從而實(shí)現(xiàn)太陽(yáng)的旋轉(zhuǎn),第二個(gè)紅色的正方形旋轉(zhuǎn)總是比第一個(gè)多45度,從而錯(cuò)開(kāi),形成8角太陽(yáng)花邊的效果。最后繪制圓形。

可以看到我們?cè)谏厦胬L制正方形的時(shí)候?yàn)?code>Paint畫(huà)筆設(shè)置了shaderLinearGradient。為什么要設(shè)置這個(gè)呢?我們可以看到兩個(gè)正方形并不是純色的,而是一個(gè)漸變色。綠色正方形是從左上角漸變至右下角(這樣↘),紅色正方形是從上到下漸變。

通過(guò)分析,動(dòng)畫(huà)的實(shí)現(xiàn)就很明了。一開(kāi)始的放大縮小動(dòng)畫(huà),需要控制圓的半徑和正方形的長(zhǎng)寬,即可以實(shí)現(xiàn)。旋轉(zhuǎn)動(dòng)畫(huà)控制sunRotateAngle來(lái)旋轉(zhuǎn)兩個(gè)正方形。

4. 太陽(yáng)陰影
太陽(yáng)陰影

太陽(yáng)的陰影就很簡(jiǎn)單了,聰明的你一下子就可以猜到用橢圓就可以很輕松的實(shí)現(xiàn)。

private void drawSunShadow(Canvas canvas) {
        mPaint.setColor(sunShadowColor);
        mPaint.setStyle(Paint.Style.FILL);
        mRectFSunShadow.set(getMeasuredWidth()/2-sunShadowWidth/2,getMeasuredHeight()- sunShadowHeight,
                getMeasuredWidth()/2+sunShadowWidth/2,getMeasuredHeight());//設(shè)置橢圓范圍
        canvas.drawOval(mRectFSunShadow,mPaint);//繪制橢圓
    }

給定一個(gè)矩形可以確定一個(gè)唯一的橢圓,因此,我們可以通過(guò)固定矩形的高度,通過(guò)變量改變矩形的寬度來(lái)控制橢圓的寬度,從而實(shí)現(xiàn)拉長(zhǎng)收縮動(dòng)畫(huà)。

5. 白云動(dòng)畫(huà)
白云動(dòng)畫(huà)

剛看到白云效果的時(shí)候,很多小伙伴都要暈了吧。這要怎么實(shí)現(xiàn)?這白云效果好惡心啊。對(duì),如果你直接使用路徑來(lái)畫(huà)的話,是有點(diǎn)惡心,而且也很難實(shí)現(xiàn)移動(dòng)放大動(dòng)畫(huà),動(dòng)的時(shí)候圓顏色還會(huì)變呢!但是如果我們換個(gè)思路呢?

a.gif

我們通過(guò)觀察動(dòng)畫(huà)可以發(fā)現(xiàn),白云是由5個(gè)圓實(shí)現(xiàn)的,然后對(duì)5個(gè)圓的位置進(jìn)行適當(dāng)?shù)臄[放。然后從底部向上緩緩出現(xiàn),并且半徑逐漸變大。同時(shí)在繪制的時(shí)候進(jìn)行截取黑色選框部分。就可以實(shí)現(xiàn)我們的白云效果了。

image.png
private void drawCloud(Canvas canvas) {
        //CircleInfo用于記錄每個(gè)圓的信息,圓心、半徑、是否可見(jiàn)
        mPath.reset();
        mPaint.setShader(mCloudLinearGradient);
        if (mCircleInfoBottomOne.isCanDraw())
            mPath.addCircle(mCircleInfoBottomOne.getX(),mCircleInfoBottomOne.getY(),mCircleInfoBottomOne.getRadius(), Path.Direction.CW);//左下1
        if (mCircleInfoBottomTwo.isCanDraw())
            mPath.addCircle(mCircleInfoBottomTwo.getX(),mCircleInfoBottomTwo.getY(),mCircleInfoBottomTwo.getRadius(), Path.Direction.CW);//底部2
        if (mCircleInfoBottomThree.isCanDraw())
            mPath.addCircle(mCircleInfoBottomThree.getX(),mCircleInfoBottomThree.getY(),mCircleInfoBottomThree.getRadius(), Path.Direction.CW);//底3
        if (mCircleInfoTopOne.isCanDraw())
            mPath.addCircle(mCircleInfoTopOne.getX(),mCircleInfoTopOne.getY(),mCircleInfoTopOne.getRadius(), Path.Direction.CW);//頂1
        if (mCircleInfoTopTwo.isCanDraw())
            mPath.addCircle(mCircleInfoTopTwo.getX(),mCircleInfoTopTwo.getY(),mCircleInfoTopTwo.getRadius(), Path.Direction.CW);//頂2
        canvas.save();
        canvas.clipRect(0,0,getMeasuredWidth(),getMeasuredHeight()/2+getMeasuredWidth()/7f);//截取黑色框部分
        canvas.drawPath(mPath,mPaint);
        canvas.restore();
        mPaint.setShader(null);
    }

白云顏色變幻效果也是通過(guò)Shader實(shí)現(xiàn)的。在這里通過(guò)Path添加各個(gè)圓進(jìn)路徑,然后通過(guò)Shader繪制整個(gè)路徑。

6. 白云陰影
白云陰影

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

private void drawCloudShadow(Canvas canvas) {
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mPaint.setColor(cloudShadowColor);
        mPaint.setAlpha(cloudShadowAlpha);
        canvas.save();
        canvas.clipRect(0,getMeasuredHeight()/2+getMeasuredWidth()/7f,getMeasuredWidth(),getMeasuredHeight());//截取多余部分
        mRectFCloudShadow.set(getMeasuredWidth()/2-finalSunWidth/2,getMeasuredHeight()/2-finalSunWidth/2,getMeasuredWidth()/2+finalSunWidth/2,getMeasuredHeight()/2+finalSunWidth/2);
        mCloudShadowPath.reset();
        mCloudShadowPath.moveTo(mCircleInfoBottomOne.getX(),getMeasuredHeight()/2+getMeasuredWidth()/7f);//白云底部一位置
        mCloudShadowPath.arcTo(mRectFCloudShadow,15,45,false);
        canvas.drawPath(mCloudShadowPath,mPaint);
        canvas.restore();
        mPaint.setAlpha(255);
    }

白色陰影的實(shí)現(xiàn)其實(shí)不算復(fù)雜,我們需要通過(guò)太陽(yáng)的圓勾畫(huà)出一條圓弧(arcTo()),然后與白云底部1/5處的點(diǎn)連線形成一個(gè)偽扇形,然后截取掉超過(guò)白云的部分,漸變效果通過(guò)變量cloudShadowAlpha改變畫(huà)筆的透明度即可。

image.png

通過(guò)以上幾個(gè)步驟的了解,我們已經(jīng)掌握了如何對(duì)動(dòng)畫(huà)進(jìn)行分解,動(dòng)畫(huà)的每個(gè)部分如何進(jìn)行繪制(在繪制動(dòng)畫(huà)時(shí),應(yīng)先找出關(guān)鍵幀或者通過(guò)動(dòng)畫(huà)最終畫(huà)面反推動(dòng)畫(huà)過(guò)程)。剩下的就是,加上屬性動(dòng)畫(huà),控制每個(gè)動(dòng)畫(huà)的播放時(shí)機(jī)。這就不作具體的講解,大家可自行查看源碼。

源碼戳我:github

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,716評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,746評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,991評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,706評(píng)論 6 413
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 56,036評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 43,203評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,725評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,451評(píng)論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,677評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,857評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,266評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,606評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,407評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,643評(píng)論 2 380

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