前些天,在IXUS上
看到一個(gè)很贊的動(dòng)畫(huà),一下子就看對(duì)眼了,于是便決定用Android來(lái)實(shí)現(xiàn)一下,效果如下:
設(shè)計(jì)在這里:Jana,點(diǎn)贊超棒的設(shè)計(jì)師
用Android
實(shí)現(xiàn)的效果如下:
現(xiàn)在開(kāi)始分析如何實(shí)現(xiàn)這個(gè)動(dòng)畫(huà):
當(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)放大縮小消失效果:
進(jìn)行剖析后,我們可以發(fā)現(xiàn)這是一個(gè)圓環(huán)放大縮小的動(dòng)畫(huà),有3個(gè)關(guān)鍵幀:
從關(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)改變ringWidth
和minRingCenterWidth
的值然后invalidate()
,即可以實(shí)現(xiàn)第一部分的圓環(huán)放大縮小效果。
2. 圓弧轉(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)動(dòng)畫(huà)下太陽(yáng)的組成成分,可以發(fā)現(xiàn)太陽(yáng)由以下二部分組成:兩個(gè)正方形和一個(gè)圓形
代碼實(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è)置了shader
為LinearGradient
。為什么要設(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)的陰影就很簡(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à)
剛看到白云效果的時(shí)候,很多小伙伴都要暈了吧。這要怎么實(shí)現(xiàn)?這白云效果好惡心啊。對(duì),如果你直接使用路徑來(lái)畫(huà)的話,是有點(diǎn)惡心,而且也很難實(shí)現(xiàn)移動(dòng)放大動(dòng)畫(huà),動(dòng)的時(shí)候圓顏色還會(huì)變呢!但是如果我們換個(gè)思路呢?
我們通過(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)我們的白云效果了。
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à)筆的透明度即可。
通過(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