Android實現(xiàn)笑臉進度加載動畫
最近看到豆瓣的笑臉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、畫眼睛
(1)初始化眼睛坐標
/** * 初始化眼睛坐標 */ 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)
這個狀態(tài)可以分為兩部分
- 嘴巴的旋轉(zhuǎn)
- 眼睛的旋轉(zhuǎn)
1、嘴巴的旋轉(zhuǎn)
開啟動畫
faceLoadingAnimator = ValueAnimator.ofFloat(0, 1).setDuration(1000); faceLoadingAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); faceLoadingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { faceValue = (float) animation.getAnimatedValue(); invalidate(); } }); //動畫延遲500ms啟動 faceLoadingAnimator.setStartDelay(200); faceLoadingAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { //恢復(fù)起始狀態(tài) currentStatus = smileStatus; } @Override public void onAnimationCancel(Animator animation) { } @Override public 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); }
這里的每次旋轉(zhuǎn)角度為startAngle。當(dāng)完全追趕上右側(cè)眼睛的時候,開始執(zhí)行自轉(zhuǎn)一周,并停止當(dāng)前動畫。
2、眼睛的旋轉(zhuǎn)
眼睛的開始旋轉(zhuǎn)速度明顯是慢于嘴巴的旋轉(zhuǎn)速度,所以每次的旋轉(zhuǎn)速度可以設(shè)置為嘴巴的一半
//畫左邊眼睛 ,旋轉(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() { @Override public void onAnimationUpdate(ValueAnimator animation) { circleValue = (float) animation.getAnimatedValue(); invalidate(); } }); circleAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { mHandler.sendEmptyMessage(3); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } });
2、重新繪制
canvas.drawArc(-radius, -radius, radius, radius, circleStartAngle + circleValue * 360, swipeAngle, false, facePaint);
四、分離狀態(tài)
主要的注意點就是眼睛的旋轉(zhuǎn)角度設(shè)置為嘴巴旋轉(zhuǎn)角度的2倍,這樣才會達到眼睛超過嘴巴的效果,主要的旋轉(zhuǎn)代碼如下:
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) @Override public 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) @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //畫布移到中間 canvas.translate(getWidth() / 2, getHeight() / 2); switch (currentStatus) { //起始狀態(tài) case smileStatus: //起始角度為0 startAngle = 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() { @Override public void onAnimationUpdate(ValueAnimator animation) { faceValue = (float) animation.getAnimatedValue(); invalidate(); } }); //動畫延遲500ms啟動 faceLoadingAnimator.setStartDelay(200); faceLoadingAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { //恢復(fù)起始狀態(tài) currentStatus = smileStatus; } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); circleAnimator = ValueAnimator.ofFloat(0, 1).setDuration(1000); circleAnimator.setInterpolator(new LinearInterpolator()); circleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { circleValue = (float) animation.getAnimatedValue(); invalidate(); } }); circleAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { mHandler.sendEmptyMessage(3); } @Override public void onAnimationCancel(Animator animation) { } @Override public 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.FaceView2 android:layout_width="match_parent" android:layout_height="match_parent"/>
完整代碼都在上面啦.
到這里就結(jié)束啦.
以上就是Android實現(xiàn)笑臉進度加載動畫的詳細內(nèi)容,更多關(guān)于Android 笑臉進度加載的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android App開發(fā)中創(chuàng)建Fragment組件的教程
這篇文章主要介紹了Android App開發(fā)中創(chuàng)建Fragment的教程,Fragment是用以更靈活地構(gòu)建多屏幕界面的可UI組件,需要的朋友可以參考下2016-05-05Android?App跳轉(zhuǎn)微信小程序踩坑實戰(zhàn)
現(xiàn)在市面上很多的應(yīng)用都可以實現(xiàn)相互跳轉(zhuǎn),下面這篇文章主要給大家介紹了關(guān)于Android?App跳轉(zhuǎn)微信小程序踩坑的相關(guān)資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2022-05-05Android view更改背景資源與padding消失的問題解決辦法
這篇文章主要介紹了Android view更改背景資源與padding消失的問題解決辦法的相關(guān)資料,需要的朋友可以參考下2017-04-04快速解決fragment中onActivityResult不調(diào)用的問題
下面小編就為大家?guī)硪黄焖俳鉀Qfragment中onActivityResult不調(diào)用的問題。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-04-04Android 獲取設(shè)備屏幕大小的幾種方法總結(jié)
這篇文章主要介紹了Android 獲取設(shè)備屏幕大小的幾種方法總結(jié)的相關(guān)資料,需要的朋友可以參考下2017-05-05Android彈出dialog后無法捕捉back鍵的解決方法
這篇文章主要為大家詳細介紹了Android彈出dialog后無法捕捉back鍵的解決方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-09-09