Android仿豆瓣笑臉進度加載
最近看到豆瓣的笑臉loading很有意思,看一張效果圖:

下面分析一下如何實現(xiàn)這樣的效果:
1、默認狀態(tài)是一張笑臉的狀態(tài)(一個嘴巴,兩個眼睛,默認狀態(tài))
2、開始旋轉(zhuǎn),嘴巴追上眼睛(合并狀態(tài))
3、追上以后自轉(zhuǎn)一周(自轉(zhuǎn)狀態(tài))
4、然后逐漸釋放眼睛(分離狀態(tài))
5、回到初始笑臉狀態(tài)(默認狀態(tài))
一、默認狀態(tài)
首先需要確定好嘴巴和眼睛的初始位置,我這里的初始化嘴巴是一個半圓,在橫軸下方。眼睛分別與橫軸夾角60度,如下圖:

這兩部分可以使用pathMeasure,我這里使用最簡單的兩個api:canvas.drawArc()和canvas.drawPoint()。
1、畫嘴巴
//畫起始笑臉canvas.drawArc(-radius, -radius, radius, radius, startAngle, swipeAngle, false,facePaint);
這里的startAngle初始值為0,swiperAngle為180,半徑radius為40。
2、畫眼睛
/*** 初始化眼睛坐標*/private void initEyes() {//默認兩個眼睛坐標位置 角度轉(zhuǎn)弧度leftEyeX = (float) (-radius * Math.cos(eyeStartAngle * Math.PI / 180));leftEyeY = (float) (-radius * Math.sin(eyeStartAngle * Math.PI / 180));rightEyeX = (float) (radius * Math.cos(eyeStartAngle * Math.PI / 180));rightEyeY = (float) (-radius * Math.sin(eyeStartAngle * Math.PI / 180));}
注意:需要將角度轉(zhuǎn)弧度
(2)開始畫眼睛
//畫起始眼睛canvas.drawPoint(leftEyeX, leftEyeY, eyePaint);canvas.drawPoint(rightEyeX, rightEyeY, eyePaint);
二、合并狀態(tài)
1、嘴巴的旋轉(zhuǎn)
faceLoadingAnimator = ValueAnimator.ofFloat(0, 1).setDuration(1000);faceLoadingAnimator.setInterpolator(new AccelerateDecelerateInterpolator());faceLoadingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {faceValue = (float) animation.getAnimatedValue();invalidate();}});//動畫延遲500ms啟動faceLoadingAnimator.setStartDelay(200);faceLoadingAnimator.addListener(new Animator.AnimatorListener() {@Overridepublic void onAnimationStart(Animator animation) {}@Overridepublic void onAnimationEnd(Animator animation) {//恢復(fù)起始狀態(tài)currentStatus = smileStatus;}@Overridepublic void onAnimationCancel(Animator animation) {}@Overridepublic void onAnimationRepeat(Animator animation) {}});
動畫執(zhí)行時間1s,記錄動畫當(dāng)前執(zhí)行進度值,存放在faceValue中。當(dāng)動畫執(zhí)行結(jié)束的時候,需要將狀態(tài)恢復(fù)到默認狀態(tài),調(diào)用invalidate的時候,進入onDraw()方法,開始重新繪制嘴巴。
//記錄時刻的旋轉(zhuǎn)角度startAngle = faceValue * 360;//追上右邊眼睛if (startAngle >= 120 + startAngle / 2) {canvas.drawArc(-radius, -radius, radius, radius, startAngle,swipeAngle, false, facePaint);//開始自轉(zhuǎn)一圈mHandler.sendEmptyMessage(2);//此時記錄自轉(zhuǎn)一圈起始的角度circleStartAngle = 120 + startAngle / 2;} else {//追眼睛的過程canvas.drawArc(-radius, -radius, radius, radius, startAngle,swipeAngle, false, facePaint);}
2、眼睛的旋轉(zhuǎn)
//畫左邊眼睛 ,旋轉(zhuǎn)的角度設(shè)置為笑臉旋轉(zhuǎn)角度的一半,這樣笑臉才能追上眼睛leftEyeX = (float) (-radius * Math.cos((60 + startAngle / 2) * Math.PI / 180));leftEyeY = (float) (-radius * Math.sin((60 + startAngle / 2) * Math.PI / 180));canvas.drawPoint(leftEyeX, leftEyeY, eyePaint);//畫右邊眼睛 ,旋轉(zhuǎn)的角度設(shè)置為笑臉旋轉(zhuǎn)角度的一半,這樣笑臉才能追上眼睛rightEyeX = (float) (radius * Math.cos((60 - startAngle / 2) * Math.PI / 180));rightEyeY = (float) (-radius * Math.sin((60 - startAngle / 2) * Math.PI / 180));canvas.drawPoint(rightEyeX, rightEyeY, eyePaint);
三、自轉(zhuǎn)狀態(tài)
1、開啟動畫
circleAnimator = ValueAnimator.ofFloat(0, 1).setDuration(1000);circleAnimator.setInterpolator(new LinearInterpolator());circleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {circleValue = (float) animation.getAnimatedValue();invalidate();}});circleAnimator.addListener(new Animator.AnimatorListener() {@Overridepublic void onAnimationStart(Animator animation) {}@Overridepublic void onAnimationEnd(Animator animation) {mHandler.sendEmptyMessage(3);}@Overridepublic void onAnimationCancel(Animator animation) {}@Overridepublic void onAnimationRepeat(Animator animation) {}});
2、重新繪制
canvas.drawArc(-radius, -radius, radius, radius,circleStartAngle + circleValue * 360,swipeAngle, false, facePaint);
四、分離狀態(tài)
startAngle = faceValue * 360;//判斷當(dāng)前笑臉的起點是否已經(jīng)走過260度 (吐出眼睛的角度,角度可以任意設(shè)置)if (startAngle >= splitAngle) {//畫左邊眼睛 ,旋轉(zhuǎn)的角度設(shè)置為笑臉旋轉(zhuǎn)角度的2倍,這樣眼睛才能快于笑臉旋轉(zhuǎn)速度leftEyeX = (float) (-radius * Math.cos((eyeStartAngle + startAngle * 2) * Math.PI / 180));leftEyeY = (float) (-radius * Math.sin((eyeStartAngle + startAngle * 2) * Math.PI / 180));canvas.drawPoint(leftEyeX, leftEyeY, eyePaint);//畫右邊眼睛 ,旋轉(zhuǎn)的角度設(shè)置為笑臉旋轉(zhuǎn)角度的2倍,這樣眼睛才能快于笑臉旋轉(zhuǎn)速度rightEyeX = (float) (radius * Math.cos((eyeStartAngle - startAngle * 2) * Math.PI / 180));rightEyeY = (float) (-radius * Math.sin((eyeStartAngle - startAngle * 2) * Math.PI / 180));canvas.drawPoint(rightEyeX, rightEyeY, eyePaint);}//畫笑臉canvas.drawArc(-radius, -radius, radius, radius, startAngle, swipeAngle,false, facePaint);
最后附上完整代碼
public class FaceView2 extends View {//圓弧半徑private int radius = 40;//圓弧畫筆寬度private float paintWidth = 15;//笑臉狀態(tài)(一個臉,兩個眼睛)private final int smileStatus = 0;//加載狀態(tài) 合并眼睛,旋轉(zhuǎn)private final int loadingStatus = 1;//合并完成 轉(zhuǎn)一圈private final int circleStatus = 2;//轉(zhuǎn)圈完成 吐出眼睛private final int splitStatus = 3;//當(dāng)前狀態(tài)private int currentStatus = smileStatus;//笑臉畫筆private Paint facePaint;//眼睛畫筆private Paint eyePaint;//笑臉開始角度private float startAngle;//笑臉弧度private float swipeAngle;//左側(cè)眼睛起點x軸坐標private float leftEyeX = 0;//左側(cè)眼睛起點y軸坐標private float leftEyeY = 0;//右側(cè)眼睛起點x軸坐標private float rightEyeX;//右側(cè)眼睛起點y軸坐標private float rightEyeY;//一開始默認狀態(tài)笑臉轉(zhuǎn)圈動畫private ValueAnimator faceLoadingAnimator;//吞并完成后,自轉(zhuǎn)一圈動畫private ValueAnimator circleAnimator;//faceLoadingAnimator動畫進度值private float faceValue;//circleAnimator動畫進度值private float circleValue;//記錄開始自轉(zhuǎn)一圈的起始角度private float circleStartAngle;//吐出眼睛的角度private float splitAngle;private float initStartAngle;//眼睛起始角度private float eyeStartAngle = 60;public FaceView2(Context context) {this(context, null);}public FaceView2(Context context, AttributeSet attrs) {this(context, attrs, 0);}public FaceView2(Context context, AttributeSet attrs,int defStyleAttr) {super(context, attrs, defStyleAttr);//自定義屬性TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FaceView2,defStyleAttr, 0);initStartAngle = typedArray.getFloat(R.styleable.FaceView2_startAngle, 0);swipeAngle = typedArray.getFloat(R.styleable.FaceView2_swipeAngle, 180);splitAngle = typedArray.getFloat(R.styleable.FaceView2_splitAngle, 260);typedArray.recycle();startAngle = initStartAngle;eyeStartAngle += startAngle;initEyes();initPaint();//開始默認動畫initAnimator();}/*** 初始化畫筆*/private void initPaint() {//初始化畫筆facePaint = new Paint();facePaint.setStrokeWidth(paintWidth);facePaint.setColor(Color.RED);facePaint.setAntiAlias(true);facePaint.setStyle(Paint.Style.STROKE);facePaint.setStrokeCap(Paint.Cap.ROUND);eyePaint = new Paint();eyePaint.setStrokeWidth(paintWidth);eyePaint.setColor(Color.RED);eyePaint.setAntiAlias(true);eyePaint.setStyle(Paint.Style.STROKE);eyePaint.setStrokeCap(Paint.Cap.ROUND);}/*** 初始化眼睛坐標*/private void initEyes() {//默認兩個眼睛坐標位置 角度轉(zhuǎn)弧度leftEyeX = (float) (-radius * Math.cos(eyeStartAngle * Math.PI / 180));leftEyeY = (float) (-radius * Math.sin(eyeStartAngle * Math.PI / 180));rightEyeX = (float) (radius * Math.cos(eyeStartAngle * Math.PI / 180));rightEyeY = (float) (-radius * Math.sin(eyeStartAngle * Math.PI / 180));}private Handler mHandler = new Handler(new Handler.Callback() {@RequiresApi(api = Build.VERSION_CODES.KITKAT)@Overridepublic boolean handleMessage(Message msg) {switch (msg.what) {case 1://啟動一開始笑臉轉(zhuǎn)圈動畫,并且開始合并眼睛currentStatus = loadingStatus;faceLoadingAnimator.start();break;case 2://暫停眼睛和笑臉動畫currentStatus = circleStatus;faceLoadingAnimator.pause();//啟動笑臉自轉(zhuǎn)一圈動畫circleAnimator.start();break;case 3://恢復(fù)笑臉轉(zhuǎn)圈動畫,并且開始分離眼睛currentStatus = splitStatus;circleAnimator.cancel();faceLoadingAnimator.resume();invalidate();break;}return false;}});@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);//畫布移到中間canvas.translate(getWidth() / 2, getHeight() / 2);switch (currentStatus) {//起始狀態(tài)case smileStatus://起始角度為0startAngle = initStartAngle;//畫起始笑臉canvas.drawArc(-radius, -radius, radius, radius, startAngle, swipeAngle, false,facePaint);//重置起始眼睛坐標initEyes();//畫起始眼睛canvas.drawPoint(leftEyeX, leftEyeY, eyePaint);canvas.drawPoint(rightEyeX, rightEyeY, eyePaint);//更改狀態(tài),進行笑臉合并眼睛mHandler.sendEmptyMessage(1);break;//合并狀態(tài)case loadingStatus://記錄時刻的旋轉(zhuǎn)角度startAngle = faceValue * 360;//追上右邊眼睛if (startAngle >= 120 + startAngle / 2) {canvas.drawArc(-radius, -radius, radius, radius, startAngle,swipeAngle, false, facePaint);//開始自轉(zhuǎn)一圈mHandler.sendEmptyMessage(2);//此時記錄自轉(zhuǎn)一圈起始的角度circleStartAngle = 120 + startAngle / 2;} else {//追眼睛的過程canvas.drawArc(-radius, -radius, radius, radius, startAngle,swipeAngle, false, facePaint);}//畫左邊眼睛 ,旋轉(zhuǎn)的角度設(shè)置為笑臉旋轉(zhuǎn)角度的一半,這樣笑臉才能追上眼睛leftEyeX = (float) (-radius * Math.cos((60 + startAngle / 2) * Math.PI / 180));leftEyeY = (float) (-radius * Math.sin((60 + startAngle / 2) * Math.PI / 180));canvas.drawPoint(leftEyeX, leftEyeY, eyePaint);//畫右邊眼睛 ,旋轉(zhuǎn)的角度設(shè)置為笑臉旋轉(zhuǎn)角度的一半,這樣笑臉才能追上眼睛rightEyeX = (float) (radius * Math.cos((60 - startAngle / 2) * Math.PI / 180));rightEyeY = (float) (-radius * Math.sin((60 - startAngle / 2) * Math.PI / 180));canvas.drawPoint(rightEyeX, rightEyeY, eyePaint);break;//自轉(zhuǎn)一圈狀態(tài) circleValue * 360 為旋轉(zhuǎn)角度case circleStatus:canvas.drawArc(-radius, -radius, radius, radius,circleStartAngle + circleValue * 360,swipeAngle, false, facePaint);break;//笑臉眼睛分離狀態(tài)case splitStatus:startAngle = faceValue * 360;//判斷當(dāng)前笑臉的起點是否已經(jīng)走過260度 (吐出眼睛的角度,角度可以任意設(shè)置)if (startAngle >= splitAngle) {//畫左邊眼睛 ,旋轉(zhuǎn)的角度設(shè)置為笑臉旋轉(zhuǎn)角度的2倍,這樣眼睛才能快于笑臉旋轉(zhuǎn)速度leftEyeX = (float) (-radius * Math.cos((eyeStartAngle + startAngle * 2) * Math.PI / 180));leftEyeY = (float) (-radius * Math.sin((eyeStartAngle + startAngle * 2) * Math.PI / 180));canvas.drawPoint(leftEyeX, leftEyeY, eyePaint);//畫右邊眼睛 ,旋轉(zhuǎn)的角度設(shè)置為笑臉旋轉(zhuǎn)角度的2倍,這樣眼睛才能快于笑臉旋轉(zhuǎn)速度rightEyeX = (float) (radius * Math.cos((eyeStartAngle - startAngle * 2) * Math.PI / 180));rightEyeY = (float) (-radius * Math.sin((eyeStartAngle - startAngle * 2) * Math.PI / 180));canvas.drawPoint(rightEyeX, rightEyeY, eyePaint);}//畫笑臉canvas.drawArc(-radius, -radius, radius, radius, startAngle, swipeAngle,false, facePaint);break;}}/*** 初始化動畫*/private void initAnimator() {faceLoadingAnimator = ValueAnimator.ofFloat(0, 1).setDuration(1000);faceLoadingAnimator.setInterpolator(new AccelerateDecelerateInterpolator());faceLoadingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {faceValue = (float) animation.getAnimatedValue();invalidate();}});//動畫延遲500ms啟動faceLoadingAnimator.setStartDelay(200);faceLoadingAnimator.addListener(new Animator.AnimatorListener() {@Overridepublic void onAnimationStart(Animator animation) {}@Overridepublic void onAnimationEnd(Animator animation) {//恢復(fù)起始狀態(tài)currentStatus = smileStatus;}@Overridepublic void onAnimationCancel(Animator animation) {}@Overridepublic void onAnimationRepeat(Animator animation) {}});circleAnimator = ValueAnimator.ofFloat(0, 1).setDuration(1000);circleAnimator.setInterpolator(new LinearInterpolator());circleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {circleValue = (float) animation.getAnimatedValue();invalidate();}});circleAnimator.addListener(new Animator.AnimatorListener() {@Overridepublic void onAnimationStart(Animator animation) {}@Overridepublic void onAnimationEnd(Animator animation) {mHandler.sendEmptyMessage(3);}@Overridepublic void onAnimationCancel(Animator animation) {}@Overridepublic void onAnimationRepeat(Animator animation) {}});}}
自定義屬性
<declare-styleable name="FaceView2"><attr name="startAngle" format="dimension" /><attr name="swipeAngle" format="dimension" /><attr name="splitAngle" format="dimension" /></declare-styleable>
布局文件中使用
<com.example.viewdemo.FaceView2android:layout_width="match_parent"android:layout_height="match_parent"/>
完整代碼都在上面啦.
到這里就結(jié)束啦.
評論
圖片
表情
