最近剛開始接觸Android動畫,然後湊齊發現豆瓣的笑臉加載動畫,覺得比較友好。百度發現已經有同學實作過了,不過和自己的想法有點不一樣,覺得有點費解,于是自己重新實作一個。那位同學的連結[https://blog.csdn.net/qinbin2015/article/details/98481620]
連結位址
實作效果

實作思路
- 整個笑臉動圖由兩部分構成,眼睛+嘴巴
- 眼睛是線性加速器LinearInterpolator,嘴巴是先加速後減速的非線性加速器AccelerateDecelerateInterpolator
- 一個完整的動畫周期,眼睛旋轉一圈(360°),嘴巴旋轉兩圈(720°)
- 和連結裡同學的差別,使用ObjectAnimator代替ValueAnimator
眼睛部分代碼實作
public class EyesView extends View {
int radius = 60;
private Paint mEyesPaint;
//左側眼睛起點x軸坐标
private float leftEyeX = 0;
//左側眼睛起點y軸坐标
private float leftEyeY = 0;
//右側眼睛起點x軸坐标
private float rightEyeX;
//右側眼睛起點y軸坐标
private float rightEyeY;
int eyeStartAngle = 60;
public EyesView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
startAnimation();
}
public EyesView(Context context){
super(context);
init();
startAnimation();
}
private void init(){
//預設兩個眼睛坐标位置 角度轉弧度
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));
mEyesPaint = new Paint();
mEyesPaint.setStrokeWidth(20.f);
mEyesPaint.setColor(Color.GREEN);
mEyesPaint.setAntiAlias(true);
mEyesPaint.setStyle(Paint.Style.STROKE);
mEyesPaint.setStrokeCap(Paint.Cap.ROUND);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getWidth() / 2, getHeight() / 2);
canvas.drawPoint(leftEyeX, leftEyeY, mEyesPaint);
canvas.drawPoint(rightEyeX, rightEyeY, mEyesPaint);
}
void startAnimation(){
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(this, "rotation",360f);
objectAnimator.setDuration(2000);
objectAnimator.setRepeatCount(Animation.INFINITE);
objectAnimator.setRepeatMode(Animation.INFINITE);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.start();
}
}
嘴巴部分代碼實作
public class SmileView extends View {
private Paint m_smilePaint;
private float startAngle = 0;
private float swipeAngle = 180;
private int radius = 60;
private SmileView(Context context){
super(context);
init();
startAnimation();
}
public SmileView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
startAnimation();
}
private void init(){
m_smilePaint = new Paint();
m_smilePaint.setStrokeWidth(20.f);
m_smilePaint.setColor(Color.GREEN);
m_smilePaint.setAntiAlias(true);
m_smilePaint.setStyle(Paint.Style.STROKE);
m_smilePaint.setStrokeCap(Paint.Cap.ROUND);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getWidth() / 2, getHeight() / 2);
canvas.drawArc(-radius, -radius, radius, radius, startAngle,
swipeAngle, false, m_smilePaint);
}
private void startAnimation(){
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(this, "rotation",720f);
objectAnimator.setDuration(2000);
objectAnimator.setRepeatCount(Animation.INFINITE);
objectAnimator.setRepeatMode(ValueAnimator.RESTART);
objectAnimator.start();
}
}
整個自定義笑臉FaceView的實作
public class FaceView extends RelativeLayout {
final static String TAG = "FaceView";
public FaceView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public FaceView(Context context){
super(context);
initView(context);
}
private void initView(Context context){
LayoutInflater.from(context).inflate(R.layout.faceview,this,true);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(getWidth() / 2, getHeight() / 2);
invalidate();
}
}
補上剩餘布局
<?xml version="1.0" encoding="utf-8"?>
<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.customView.douban.FaceView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>