讀律看書三九年,烏紗頭上有青天,男兒欲畫凌煙閣,第一功名不愛錢。
不知道大家關注過沒有,在你使用支付寶的過程中,有一個支付成功的動畫,雖然說很小,但看起來其實還是蠻實用的,涉及的知識點有Android屬性動畫,Paint的getSegment()函數。
1.分析動畫
首先,我們來分析一下這個動畫,如上圖所示,我們可以看到,先是圓自己運行閉合,然後在在圓的裏面畫一個✔,對於圓來說,很簡單,無外乎定位圓心與半徑就行,難點在於✔的設計,所以我們先用一張圖來分析一下,如下圖所示:
假設圓的半徑爲mRadius,圓心O座標爲(X,Y),我們把對勾的起點放在A點,假設A的座標爲在圓心平行線中間,那麼A的座標就是(X-mRadius/2,Y),同理,我們假設B的座標也在圓心垂直線下中心,那麼B的座標爲(X,Y+mRadius/2),因爲在屏幕中,原點在屏幕的右上角,正方向X軸向右,正方向Y軸向下,所以這裏是加,同理,C點肯定要接近圓的邊緣,所以C的X軸肯定大於mRadius的一半,姑且我們這裏設爲X+mRadius/2,而Y也相對來說在上邊,而Y不能超出圓外,所以我們根據cX = x + r * cos(a * π / 180);cY = y + r * sin(a * π / 180),計算出該X座標在圓圈的座標,小於這個座標的Y軸設置就行,這裏小編C點設置爲(x+mRadius/2,Y-mRadius/3)。
2.自定義View
原理我們分析清楚了,下面就可以自定義View,來實現支付寶支付成功的動畫了,首先,老樣子與刮一刮類似,在Android Studio中間創建一個類繼承View,實現如下三個構造函數:
public class AlipayView extends View {
public AlipayView(Context context) {
super(context);
}
public AlipayView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public AlipayView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
接着我們定義成員變量,如下圖所示:
private Paint paint;//畫筆工具
private Path circlePath,dstPath;//圓路徑,截取路徑
private PathMeasure pathMeasure;//計算路徑的參數
private float mCurrentValue;//動畫執行的進度
private int X,Y,mRadius;//圓心座標與半徑
這裏,我們定義了一個畫筆工具,兩個路徑,以及計算路徑個參數的計算器類,以及動畫執行的進度,所有用到的成員變量都在這裏,接着就是初始化我們的AlipayView。
public AlipayView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
setLayerType(LAYER_TYPE_SOFTWARE,null);//關閉硬件加速
this.paint=new Paint(Paint.ANTI_ALIAS_FLAG);//初始化畫筆,且設置爲抗鋸齒
this.paint.setStrokeWidth(4);//設置畫筆寬度
this.paint.setStyle(Paint.Style.STROKE);//設置描邊
this.dstPath=new Path();//初始化
this.circlePath=new Path();//初始化
this.circlePath.addCircle(X,Y,mRadius,Path.Direction.CW);//順時針畫圓
//下面三行代碼畫的是勾的路徑,對照上面分析圖
this.circlePath.moveTo(X-mRadius/2,Y);
this.circlePath.lineTo(X,Y+mRadius/2);
this.circlePath.lineTo(X+mRadius/2,Y-mRadius/3);
this.pathMeasure=new PathMeasure(this.circlePath,false);//不閉合
ValueAnimator valueAnimator=ValueAnimator.ofFloat(0,2);//這裏分兩段動畫,一段畫圓,一段畫勾
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurrentValue=(float)animation.getAnimatedValue();//獲取動畫進度
invalidate();//獲取到進度後進行重繪,會調用onDraw(Canvas canvas)
}
});
valueAnimator.setDuration(4000);//每次動畫時間
valueAnimator.start();//執行動畫
}
這裏我們初始化View主要做了三件事,第一初始化各個成員變量,其二將圓的路徑與勾的路徑初始化,但並沒有繪製,繪製都在onDraw(Canvas canvas)函數裏面,第三,初始化動畫的參數,第四,獲取路徑的計算器,計算器包含路徑的一些信息(pathMeasure)。
3.onDraw(canvas)繪製動畫
初始化各個成員變量以及動畫參數後,最後就是要將我們的這些信息,繪製到屏幕上去,而自定義View繪製函數是onDraw(Canvas),代碼如下:
private boolean mNext=false;//判斷是否指閉合
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.WHITE);//首先繪製背景色爲白色
if(this.mCurrentValue<1){
//初始化函數中說過,mCurrentValue是記錄動畫的進度的,而小編將動畫進度設置爲2,(0,1)就是畫圓圈,(1,2)就是畫對勾
//而且mCurrentValue的進度是隨機的,並不一定獲取到1,所以別拿等等與1來計算,只要大於1就執行畫對勾就行
float stop=this.pathMeasure.getLength()*this.mCurrentValue;//計算當前進度下路徑的百分比,比如,0.25畫到圓的4分之一,那麼整個圓繪畫進度長度就在這裏計算得到。
//前面說過dstPath是截取路徑,比如一張圖片長200,我截取一半就有100,同樣通過pathMeasure.getSegment就可以截取circlePath的當前進度路徑。
this.pathMeasure.getSegment(0,stop,dstPath,true);
}else{
if(!mNext){
this.mNext=true;
//剛說過了,動畫的進度值並不一定會獲取到1,有可能直接從0.99跳到1.01,那麼沒繪製完成的部分,就需要繪製先繪製完成
this.pathMeasure.getSegment(0,this.pathMeasure.getLength(),dstPath,true);
this.pathMeasure.nextContour();//因爲圓與對勾並沒有閉合,所以算兩個路徑,這句代碼就是切換到對勾路徑上
}
float stop=this.pathMeasure.getLength()*(this.mCurrentValue-1);//每條進度都是按1算百分比的, 但動畫設置的是2,所以減去圓的1,單獨計算勾的路徑百分比
this.pathMeasure.getSegment(0,stop,dstPath,true);
}
canvas.drawPath(dstPath,paint);//把截取到的路徑畫出來
}
這段首先繪製自定義控件的背景爲白色,然後判斷動畫的進度,如果小於1,執行圓的生成動畫,通過PathMeasure.getSegment將截取的路徑存儲到dstPath中,而當動畫進度不小於1的時候, 因爲動畫的插值器並不一定會獲取到1這個值,所以接下來首先做的就是先閉合這個圓,然後通過next.Contour()切換到勾的路徑,在根據動畫的值繪製勾出來,最後調用canvas將截取的路徑畫到自定義View上面,也就是屏幕上。
private int X=500,Y=500,mRadius=250;//圓心座標
當然照搬上面代碼肯定什麼都沒有,因爲X,Y,mRaduis,都沒有賦值,所以根據自己的需求調整大小,或者設置一個函數,讓外部調用設置靈活的X,Y,半徑都行,代碼如上。
XML佈局文件代碼如下:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.liyuanjinglyj.alipayapplication.AlipayView
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
本文代碼Github下載地址:點擊下載