怎么在Android中實現一個笑臉進度加載動畫?相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
Android是一種基于Linux內核的自由及開放源代碼的操作系統,主要使用于移動設備,如智能手機和平板電腦,由美國Google公司和開放手機聯盟領導及開發。
首先需要確定好嘴巴和眼睛的初始位置,我這里的初始化嘴巴是一個半圓,在橫軸下方。眼睛分別與橫軸夾角60度,如下圖:

這兩部分可以使用pathMeasure,我這里使用最簡單的兩個api:canvas.drawArc()和canvas.drawPoint()。
//畫起始笑臉 canvas.drawArc(-radius, -radius, radius, radius, startAngle, swipeAngle, false,facePaint);
這里的startAngle初始值為0,swiperAngle為180,半徑radius為40。
(1)初始化眼睛坐標
/**
* 初始化眼睛坐標
*/
private void initEyes() {
//默認兩個眼睛坐標位置 角度轉弧度
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));
}注意:需要將角度轉弧度
(2)開始畫眼睛
//畫起始眼睛 canvas.drawPoint(leftEyeX, leftEyeY, eyePaint); canvas.drawPoint(rightEyeX, rightEyeY, eyePaint);
這個狀態可以分為兩部分
嘴巴的旋轉
眼睛的旋轉
開啟動畫
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) {
//恢復起始狀態
currentStatus = smileStatus;
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});動畫執行時間1s,記錄動畫當前執行進度值,存放在faceValue中。當動畫執行結束的時候,需要將狀態恢復到默認狀態,調用invalidate的時候,進入onDraw()方法,開始重新繪制嘴巴。
//記錄時刻的旋轉角度
startAngle = faceValue * 360;
//追上右邊眼睛
if (startAngle >= 120 + startAngle / 2) {
canvas.drawArc(-radius, -radius, radius, radius, startAngle,
swipeAngle, false, facePaint);
//開始自轉一圈
mHandler.sendEmptyMessage(2);
//此時記錄自轉一圈起始的角度
circleStartAngle = 120 + startAngle / 2;
} else {
//追眼睛的過程
canvas.drawArc(-radius, -radius, radius, radius, startAngle,
swipeAngle, false, facePaint);
}這里的每次旋轉角度為startAngle。當完全追趕上右側眼睛的時候,開始執行自轉一周,并停止當前動畫。
眼睛的開始旋轉速度明顯是慢于嘴巴的旋轉速度,所以每次的旋轉速度可以設置為嘴巴的一半
//畫左邊眼睛 ,旋轉的角度設置為笑臉旋轉角度的一半,這樣笑臉才能追上眼睛 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); //畫右邊眼睛 ,旋轉的角度設置為笑臉旋轉角度的一半,這樣笑臉才能追上眼睛 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);
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) {
}
});canvas.drawArc(-radius, -radius, radius, radius, circleStartAngle + circleValue * 360, swipeAngle, false, facePaint);
主要的注意點就是眼睛的旋轉角度設置為嘴巴旋轉角度的2倍,這樣才會達到眼睛超過嘴巴的效果,主要的旋轉代碼如下:
startAngle = faceValue * 360;
//判斷當前笑臉的起點是否已經走過260度 (吐出眼睛的角度,角度可以任意設置)
if (startAngle >= splitAngle) {
//畫左邊眼睛 ,旋轉的角度設置為笑臉旋轉角度的2倍,這樣眼睛才能快于笑臉旋轉速度
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);
//畫右邊眼睛 ,旋轉的角度設置為笑臉旋轉角度的2倍,這樣眼睛才能快于笑臉旋轉速度
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;
//笑臉狀態(一個臉,兩個眼睛)
private final int smileStatus = 0;
//加載狀態 合并眼睛,旋轉
private final int loadingStatus = 1;
//合并完成 轉一圈
private final int circleStatus = 2;
//轉圈完成 吐出眼睛
private final int splitStatus = 3;
//當前狀態
private int currentStatus = smileStatus;
//笑臉畫筆
private Paint facePaint;
//眼睛畫筆
private Paint eyePaint;
//笑臉開始角度
private float startAngle;
//笑臉弧度
private float swipeAngle;
//左側眼睛起點x軸坐標
private float leftEyeX = 0;
//左側眼睛起點y軸坐標
private float leftEyeY = 0;
//右側眼睛起點x軸坐標
private float rightEyeX;
//右側眼睛起點y軸坐標
private float rightEyeY;
//一開始默認狀態笑臉轉圈動畫
private ValueAnimator faceLoadingAnimator;
//吞并完成后,自轉一圈動畫
private ValueAnimator circleAnimator;
//faceLoadingAnimator動畫進度值
private float faceValue;
//circleAnimator動畫進度值
private float circleValue;
//記錄開始自轉一圈的起始角度
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() {
//默認兩個眼睛坐標位置 角度轉弧度
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:
//啟動一開始笑臉轉圈動畫,并且開始合并眼睛
currentStatus = loadingStatus;
faceLoadingAnimator.start();
break;
case 2:
//暫停眼睛和笑臉動畫
currentStatus = circleStatus;
faceLoadingAnimator.pause();
//啟動笑臉自轉一圈動畫
circleAnimator.start();
break;
case 3:
//恢復笑臉轉圈動畫,并且開始分離眼睛
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) {
//起始狀態
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);
//更改狀態,進行笑臉合并眼睛
mHandler.sendEmptyMessage(1);
break;
//合并狀態
case loadingStatus:
//記錄時刻的旋轉角度
startAngle = faceValue * 360;
//追上右邊眼睛
if (startAngle >= 120 + startAngle / 2) {
canvas.drawArc(-radius, -radius, radius, radius, startAngle,
swipeAngle, false, facePaint);
//開始自轉一圈
mHandler.sendEmptyMessage(2);
//此時記錄自轉一圈起始的角度
circleStartAngle = 120 + startAngle / 2;
} else {
//追眼睛的過程
canvas.drawArc(-radius, -radius, radius, radius, startAngle,
swipeAngle, false, facePaint);
}
//畫左邊眼睛 ,旋轉的角度設置為笑臉旋轉角度的一半,這樣笑臉才能追上眼睛
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);
//畫右邊眼睛 ,旋轉的角度設置為笑臉旋轉角度的一半,這樣笑臉才能追上眼睛
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;
//自轉一圈狀態 circleValue * 360 為旋轉角度
case circleStatus:
canvas.drawArc(-radius, -radius, radius, radius,
circleStartAngle + circleValue * 360,
swipeAngle, false, facePaint);
break;
//笑臉眼睛分離狀態
case splitStatus:
startAngle = faceValue * 360;
//判斷當前笑臉的起點是否已經走過260度 (吐出眼睛的角度,角度可以任意設置)
if (startAngle >= splitAngle) {
//畫左邊眼睛 ,旋轉的角度設置為笑臉旋轉角度的2倍,這樣眼睛才能快于笑臉旋轉速度
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);
//畫右邊眼睛 ,旋轉的角度設置為笑臉旋轉角度的2倍,這樣眼睛才能快于笑臉旋轉速度
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) {
//恢復起始狀態
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"/>
看完上述內容,你們掌握怎么在Android中實現一個笑臉進度加載動畫的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。