看到支付寶的下拉重新整理有一個笑臉的動畫,因此自己也動手實現一下。效果圖如下:
一、總體思路
1、靜態部分的笑臉。
這一部分的笑臉就是一個半圓弧,加上兩顆眼睛,這部分比較簡單,用於一開始的展示。
2、動態笑臉的實現。
2.1、先是從底部有一個圓形在運動,運動在左眼位置時把左眼給繪製,同時圓形繼續運動,運動到右眼位置時繪製右眼,圓形繼續運動到最右邊的位置。
2.2、當上面的圓形運動到最右邊時候,開始不斷繪製臉,從右向左,臉不斷增長,這裡臉設定為接近半個圓形的大小。
2.3、當臉畫完的時候,開始讓臉旋轉起來,就是一邊在增長的同時,另一邊是在縮短的。
2.4、最後臉的部分是慢慢縮為一個點的,此時動畫結束。
2.5、時間可以看做時最底部的那個圓形運動了兩周,因此可以用分數來表示每一部分的運動,如從底部開始到左眼睛的位置,用時比例為(1/4+1/8),最終控制每一部分的動畫比例的和加起來為2即可。
大概是這樣的時間比例:(1/4+1/8) + (1/4) +(1/8) +(1/2) +(1/4) +(1/4+1/4) ,其中1/4代表1/4個圓弧,也是1/4的時間,其它的類似。
二、代碼實現
1、重寫onMeasure()方法
處理為wrap_content情況,那麼它的specMode是AT_MOST模式,在這種模式下它的寬/高等於spectSize,這種情況下view的spectSize是parentSize,而parentSize是父容器目前可以使用大小,就是父容器當前剩餘的空間大小, 就相當於使用match_parent一樣 的效果,因此我們可以設定一個預設的值。
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int widthSpectMode = MeasureSpec.getMode(widthMeasureSpec);int widthSpectSize = MeasureSpec.getSize(widthMeasureSpec);int heightSpectMode = MeasureSpec.getMode(heightMeasureSpec);int heightSpectSize = MeasureSpec.getSize(heightMeasureSpec);if (widthSpectMode == MeasureSpec.AT_MOST&& heightSpectMode == MeasureSpec.AT_MOST) {setMeasuredDimension(mWidth, mHeight);} else if (widthSpectMode == MeasureSpec.AT_MOST) {setMeasuredDimension(mWidth, heightSpectSize);} else if (heightSpectMode == MeasureSpec.AT_MOST) {setMeasuredDimension(widthSpectSize, mHeight);}}
2、在建構函式中調用init()方法
進行初始化,之所以看到運動中圓弧能夠在右邊增長的同時,左邊的也在減少是使用PathMeasure類中的getSegment方法來截取任意一段長度的路徑。
private void init(Context context, AttributeSet attrs) {drawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG| Paint.FILTER_BITMAP_FLAG);lineWidth = dip2px(context, lineWidth);radius = dip2px(context, radius);path = new Path();pathCircle = new Path();pathCircle2 = new Path();//在path中添加一個順時針的圓,這時候路徑的起點和終點在最後邊//在畫前半部分的臉和運動中的臉,起點在最右邊比較方便的計算,但在最後那部分,運動的終點//是在圓形的底部,這樣把路徑圓進行轉換到底部,方便計算pathCircle.addCircle(0, 0, radius, Direction.CW);pathCircle2.addCircle(0, 0, radius, Direction.CW);//利用Matrix,讓pathCircle中的圓旋轉90度,這樣它的路徑的起點和終點都在底部了Matrix m = new Matrix();m.postRotate(90);pathCircle.transform(m);//畫臉的筆paint = new Paint();//畫眼睛的筆eyePaint = new Paint();paint.setColor(blue);eyePaint.setColor(blue);paint.setStyle(Paint.Style.STROKE);paint.setStrokeWidth(lineWidth);eyePaint.setStrokeWidth(lineWidth);//設定畫臉的筆的端點為圓角(即起點和終點都是圓角)paint.setStrokeCap(Paint.Cap.ROUND);//使用PathMeasure計算路徑的資訊pm = new PathMeasure();pm.setPath(pathCircle, false);pm2 = new PathMeasure();pm2.setPath(pathCircle2, false);//路徑的長度,兩個路徑都是圓形,因此只計算其中一個即可length = pm.getLength();eyeRadius = (float)(lineWidth/2.0+lineWidth/5.0);}
3、畫靜態笑臉
pm2.getSegment()方法可以擷取指定長度的路徑,然後儲存在path中,在第二步已經把一個圓加到path中去,並初始化了pm2了。
/**靜態笑臉* @param canvas*/private void first(Canvas canvas) {pm2.getSegment(10, length / 2-10, path, true);canvas.drawPath(path, paint);path = new Path();drawEye(canvas);}/**一開始畫的眼睛* @param canvas*/public void drawEye(Canvas canvas) {float x = (float) ((radius) * Math.cos(Math.PI * 45 / 180));float y = x;canvas.drawCircle(-x, -y, eyeRadius , eyePaint);canvas.drawCircle(x, -y, eyeRadius , eyePaint);}
4、實現運動的圓形的方法,即動畫開始部分。
這裡記得要進行角度轉換,π=180
/**從底部開始畫一個在運動的圓,運動時間為0-3/4* 即從270度開始,逆時針到0度* @param canvas*/private void drawCircle(Canvas canvas) {float degree = 270 - 270 * 4 / 3 * fraction;float x = (float) ((radius ) * Math.cos(Math.PI * degree/180));float y = -(float) ((radius ) * Math.sin(Math.PI * degree/ 180));canvas.drawCircle(x, y, eyeRadius, eyePaint);}
5、在圓形運動的同時畫眼睛
在圓形運動到左眼的位置時,同時繪製左眼,時間為1/4+1/8=3/8
運動到右眼位置時繪製右眼,時間為1/4+1/8+1/4=5/8
兩個眼睛的位置都設為45度
/* @param canvas* @param pos 0代表畫左眼,1代表畫右眼*/public void drawOneEye(Canvas canvas, int pos) {float x = (float) ((radius) * Math.cos(Math.PI * 45 / 180));float y = x;if (pos == 0) {canvas.drawCircle(-x, -y, eyeRadius, eyePaint);}else if(pos==1){canvas.drawCircle(x, -y, eyeRadius , eyePaint);}}
6、動畫進行之後繪製笑臉
笑臉部分是半個圓,因此截取的最大長度是length/2
用的時間是1/2,畫完之後fraction應該到了5/4的時間了,1/4+1/8+1/4+1/8+1/2=5/4
public void drawFace(Canvas canvas){//需要重新給path賦值path=null;path = new Path();//根據時間來截取一定長度的路徑,儲存到path中,取值範圍(0,length/2)pm2.getSegment(0, (float) (length*(fraction-0.75)), path, true);canvas.drawPath(path, paint);}
7、笑臉繪製完成後,把它動起來。
把圓臉部分逆時針旋轉起來,截取最大長度還是length/2, 用的是這個方法pm2.getSegment(),運動的時間為1/4時間,需要不斷改變起點和終點,這樣圓臉才會動起來
public void rotateFace(Canvas canvas){path=null;path = new Path();pm2.getSegment((float)(length*(fraction-5.0/4)), (float)(length*(fraction-5.0/4)+length*0.5), path, true);canvas.drawPath(path, paint);}
8、最後那部分動畫的實現。
剩下的1/4時間,就用另外一個PathMeasure,這個圓的路徑起點是底部的,在初始化時候已經進行轉換,因為這樣設定比較方便的計算它的終點位置。
public void drawLastFact(Canvas canvas){path = null;path = new Path();//從起點的1/4長度開始(即最左邊的圓點),到圓的路徑的終點(即底部)pm.getSegment((float)(1.0/4*length+3.0/2*(fraction-3.0/2)*length), (float)(length/2.0+length/8.0+(fraction-3.0/2)*length), path, true);canvas.drawPath(path, paint);}
9、屬性動畫的實現
public void performAnim() {//上面計算的時間比例,加起來就是2,是運動了兩周,因此這裡設定為(0,2)val = ValueAnimator.ofFloat(0, 2);val.setDuration(duration);val.addUpdateListener(new AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator arg0) {fraction = (float) arg0.getAnimatedValue();postInvalidate();}});val.setRepeatCount(repeaCount);val.start();val.setRepeatMode(ValueAnimator.RESTART);}
10 、在onDraw()方法中調用一上方法。
這裡的fraction的範圍是[0,2],每個片段就用分數表示,最後它們的和剛好是2。
@Overrideprotected void onLayout(boolean changed, int left, int top, int right,int bottom) {super.onLayout(changed, left, top, right, bottom);if (changed) {if (changed) {mWidth = right - left;mHeight = bottom - top;}}}@Overrideprotected void onDraw(Canvas canvas) {//從畫布上去除鋸齒canvas.setDrawFilter(drawFilter);canvas.translate(mWidth / 2, mHeight / 2);if (fraction == -1||!val.isRunning())first(canvas);//從底部開始畫一個在運動的圓,運動時間為0-3/4if (0 < fraction && fraction < 0.75) {drawCircle(canvas);}//畫左眼if (fraction > 1.0 * 3 / 8&&fraction<1.0*6/4) {drawOneEye(canvas,0);}//畫右眼if(fraction>1.0*5/8&&fraction<1.0*6/4){drawOneEye(canvas, 1);}//畫臉if(fraction>=0.75&&fraction<=5.0/4){drawFace(canvas);}//把臉運動起來if(fraction>=5.0/4&&fraction<=(5.0/4+1.0/4)){rotateFace(canvas);}if(fraction>=6.0/4){drawLastFact(canvas);}}
11、其它的方法和欄位的定義
/*** 根據手機的解析度從 dp 的單位 轉成為 px(像素)*/public int dip2px(Context context, float dpValue) {final float scale = context.getResources().getDisplayMetrics().density;return (int) (dpValue * scale + 0.5f);}//欄位private final int blue = 0xff4aadff;private int mWidth = 200;private int mHeight = 200;private int radius = 20;private int lineWidth = 5;private float eyeRadius;Paint paint,eyePaint;DrawFilter drawFilter;Path path, pathCircle,pathCircle2;PathMeasure pm,pm2;float length;// 圓周長float fraction = -1;long duration = 2000;int repeaCount = 8;float x = 0, y = 0;ValueAnimator val;
11、自訂控制項的使用
//在布局中的設定<com.example.test22.view.SmileView android:id="@+id/smile"android:layout_width="match_parent"android:layout_height="100dp"/>
在Activity中,
SmileView smile;smile = (SmileView)findViewById(R.id.smile);//設定動畫執行時間,重複的次數。smile.setDuration(2000);smile.setRepeaCount(8);//執行動畫smile.performAnim();//停止動畫smile.cancelAnim();
三、總結
我覺得痛點在於運動中圓弧的一邊增長的同時,另一邊在縮短的控制,計算的不好就很容易出現從一個片段到另外一個片段時候跳躍十分明顯,在這裡我用到兩個路徑的圓,一個圓的起點在最右邊,一個圓起點在底部,這樣在處理最後那部分,片段的終點需要回到底部時候比較方便。重點在於PathMeasure類的getSegment()方法的運用,同時會改變預設路徑的起點。
以上所述是小編給大家介紹的Android仿支付寶笑臉重新整理載入動畫的實現代碼,希望對大家有所協助,如果大家有任何疑問請給我留言,小編會及時回複大家的。在此也非常感謝大家對雲棲社區網站的支援!