前些天,在IXUS上看到一個很贊的動畫,一下子就看對眼了,于是便決定用Android來實(shí)現(xiàn)一下,效果如下:

設(shè)計在這里:Jana,點(diǎn)贊超棒的設(shè)計師
用Android實(shí)現(xiàn)的效果如下:

現(xiàn)在開始分析如何實(shí)現(xiàn)這個動畫:

當(dāng)我們看到一個動畫要實(shí)現(xiàn)的時候,很多朋友可能就直接照著開始實(shí)現(xiàn)了,其實(shí)這樣是很難實(shí)現(xiàn)。我們在接觸到一個新動畫的時候,首先要對動畫進(jìn)行分解。例如本動畫,我們進(jìn)行分析后,可以把動畫分為以下幾個部分:
1. 圓環(huán)放大縮小消失效果:

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



從關(guān)鍵幀1->2->3->圓環(huán)消失,那對我們來說就是使用屬性動畫進(jìn)行繪制圓環(huán),我是通過繪制兩個圓形成圓環(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);//肉圓小圓
}
這里是通過控制大圓和小圓的半徑大小,從而來控制圓環(huán)的位置和大小。然后只需要通過ValueAnimator動態(tài)改變ringWidth和minRingCenterWidth的值然后invalidate(),即可以實(shí)現(xiàn)第一部分的圓環(huán)放大縮小效果。
2. 圓弧轉(zhuǎn)動縮小動畫

這里其實(shí)就是處于不同圓下的兩條圓弧,在做旋轉(zhuǎn)和長度變化動畫。
我們先來看一下圓弧的方法:
canvas.drawArc(圓弧所在的圓的正方形框,開始角度,跨越角度,是否要連接圓心,mPaint);
那么我們就可以通過動態(tài)改變開始角度和跨越角度來實(shí)現(xiàn)圓弧的移動和長度變化,這里的圓弧也是有3個關(guān)鍵幀,圓弧長度初始狀態(tài)(外弧90度,內(nèi)弧2度)->圓弧長度達(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);//通過畫筆的寬度來控制圓弧的寬度
canvas.drawArc(mRectCenterArc, centerArcEndAngle-centerArcAngle,centerArcAngle,false,mPaint);//畫內(nèi)弧
mPaint.setStrokeWidth(getMeasuredWidth()/25);
canvas.drawArc(mRectOutSideArc,outSideArcStartAngle,outSideArcAngle,false,mPaint);//畫外弧
}
細(xì)心的小伙伴可能已經(jīng)發(fā)現(xiàn)了,畫外弧和內(nèi)弧的參數(shù)還像有點(diǎn)不太一樣。因?yàn)閮?nèi)弧是順時針旋轉(zhuǎn),外弧是逆時針旋轉(zhuǎn)的,為了保證弧轉(zhuǎn)動到某一點(diǎn)后不再移動,我們做了處理,內(nèi)弧以逆向的思維來做。(可以編寫代碼試下這么做有什么好處)
3. 太陽出現(xiàn)以及旋轉(zhuǎn)

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

代碼實(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);//第二個正方形比第一個正方形多旋轉(zhuǎn)45度
mPaint.setShader(mFlowerRotateLinearGradient);
canvas.drawRect(mRectFSunFlower,mPaint);
canvas.restore();
mPaint.setShader(null);
canvas.drawCircle(getMeasuredWidth()/2,getMeasuredHeight()/2,sunWidth/2,mPaint);//畫圓
}
這里我們可以先畫第一個綠色的正方形,然后畫第二個紅色的正文形,這里通過一個變量sunRotateAngle來控制兩個正方形的旋轉(zhuǎn),從而實(shí)現(xiàn)太陽的旋轉(zhuǎn),第二個紅色的正方形旋轉(zhuǎn)總是比第一個多45度,從而錯開,形成8角太陽花邊的效果。最后繪制圓形。
可以看到我們在上面繪制正方形的時候?yàn)?code>Paint畫筆設(shè)置了shader為LinearGradient。為什么要設(shè)置這個呢?我們可以看到兩個正方形并不是純色的,而是一個漸變色。綠色正方形是從左上角漸變至右下角(這樣↘),紅色正方形是從上到下漸變。
通過分析,動畫的實(shí)現(xiàn)就很明了。一開始的放大縮小動畫,需要控制圓的半徑和正方形的長寬,即可以實(shí)現(xiàn)。旋轉(zhuǎn)動畫控制sunRotateAngle來旋轉(zhuǎn)兩個正方形。
4. 太陽陰影

太陽的陰影就很簡單了,聰明的你一下子就可以猜到用橢圓就可以很輕松的實(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);//繪制橢圓
}
給定一個矩形可以確定一個唯一的橢圓,因此,我們可以通過固定矩形的高度,通過變量改變矩形的寬度來控制橢圓的寬度,從而實(shí)現(xiàn)拉長收縮動畫。
5. 白云動畫

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

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

private void drawCloud(Canvas canvas) {
//CircleInfo用于記錄每個圓的信息,圓心、半徑、是否可見
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);
}
白云顏色變幻效果也是通過Shader實(shí)現(xiàn)的。在這里通過Path添加各個圓進(jìn)路徑,然后通過Shader繪制整個路徑。
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ù)雜,我們需要通過太陽的圓勾畫出一條圓弧(arcTo()),然后與白云底部1/5處的點(diǎn)連線形成一個偽扇形,然后截取掉超過白云的部分,漸變效果通過變量cloudShadowAlpha改變畫筆的透明度即可。

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