自定view--小清新載入進度動畫特效
阿新 • • 發佈:2018-12-16
實現上面的效果涉及到左右兩個小圓點、中間小圓點、線條的繪製,同時涉及到屬性動畫及二階貝塞爾曲線的使用。
A點和B點的兩個小圓點比較好繪製,確定相應的座標後,呼叫canvas.drawCircle()方法進行繪製;
A點的X就是:getWidth(自定義控制元件的寬度)/2-mLineWidth(A點到B點的長度,即線條的長度)/2;
C點的X就是:getWidth(自定義控制元件的寬度)/2+mLineWidth(A點到B點的長度,即線條的長度)/2;
A點和C點的Y是一樣的都是:getHeight()/2
接下來就是繪製線條和中間的小球,將小球的狀態定義為3種:下墜,上升及自由落體,在小球下墜、上升或者自由落體時,呼叫mPath.moveTo()和mPath.quadTo()進行左右貝塞爾曲線的繪製,A點和C點的座標在繪製左右兩個小點的時候就已經得到了,
B點的X座標是:getWidth(自定義控制元件的寬度)/2+mLineWidth(A點到B點的長度,即線條的長度)/2;
Y的座標在上升或者下墜過程中就有區別:下墜Y座標:getHeight() / 2 + mDownDistance(下墜的距離);上升或者自由落地Y座標:getHeight() / 2 + (distance(定義的一個距離常量) - mUpDistance(上升的距離))
D和E點的座標可以通過除錯後獲取到最佳的座標點,相應的三個點的座標值獲取後,就可以繪製相應的貝塞爾曲線了;
剩下就只有中間小球的座標位置了,不管下墜、上升或者自由落體,X對應的座標都是getWidth(自定義控制元件的寬度)/2;
但是對應的Y的座標就會有區別:
下墜:getHeight() / 2 + mDownDistance(下墜的距離) - mPointSize(小球的半徑)
正常上升:getHeight() / 2 + (distance - mUpDistance) - mPointSize
自由落地:getHeight() / 2 - freeBallDistance(自由落體的距離) - mPointSize
獲取到對應的座標點後,呼叫onDraw()方法進行繪製,小球的上下運動是通過ValueAnimator來控制的;
實現主要程式碼(程式碼中有相應的註釋):
public class LoadView extends SurfaceView implements SurfaceHolder.Callback {
private static final int STATE_DOWN = 1;
private static final int STATE_UP = 2;
private Paint mPaint;
private Path mPath;
//線的顏色
private int mLineColor = Color.WHITE;
//點的顏色
private int mPointColor = Color.BLACK;
//點的大小
private int mPointSize = 10;
//線的寬度
private int mLineWidth = 200;
//線的高度
private int mLineHeight = 2;
//往下走的距離
private float mDownDistance;
private static final int distance=50;
//往上走的距離
private float mUpDistance;
private float freeBallDistance;
//向下運動
private ValueAnimator downController;
//向上運動
private ValueAnimator upController;
//自由落體
private ValueAnimator freeDownController;
//動畫集合
private AnimatorSet animatorSet;
private int state;
//是否向上彈起
private boolean isBounced = false;
private boolean isBallFreeUp = false;
private boolean isUpControllerDied = false;
private boolean isAnimationShowing = false;
public LoadView(Context context) {
this(context, null);
}
public LoadView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LoadView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
/**
* 初始化
*
* @param context
* @param attrs
*/
private void init(Context context, AttributeSet attrs) {
//初始化自定義屬性
initAttributes(context, attrs);
//初始化畫筆
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(mLineHeight);
mPaint.setStrokeCap(Paint.Cap.ROUND);//圓形線帽
//初始化path
mPath = new Path();
getHolder().addCallback(this);
initController();
}
private void initController() {
downController = ValueAnimator.ofFloat(0, 1);
//設定動畫持續的時長
downController.setDuration(500);
//設定差值器
downController.setInterpolator(new DecelerateInterpolator());
//監聽動畫執行
downController.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mDownDistance = distance * (float) animation.getAnimatedValue();
//進行重繪
postInvalidate();
}
});
downController.addListener(new SimpleAnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
state = STATE_DOWN;
}
});
upController = ValueAnimator.ofFloat(0, 1);
upController.setDuration(900);
upController.setInterpolator(new DampingInterpolator());
upController.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mUpDistance = distance * (float) animation.getAnimatedValue();
if (mUpDistance >= distance) {
//進入自由落體狀態
isBounced = true;
if (!freeDownController.isRunning() &&
!freeDownController.isStarted() &&
!isBallFreeUp) {
freeDownController.start();
}
}
postInvalidate();
}
});
upController.addListener(new SimpleAnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
state = STATE_UP;
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
isUpControllerDied=true;
}
});
freeDownController = ValueAnimator.ofFloat(0, 6.8f);
freeDownController.setDuration(600);
freeDownController.setInterpolator(new DecelerateInterpolator());
freeDownController.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//一個公式解決上升減速和下降加速
float t = (float) animation.getAnimatedValue();
freeBallDistance = 34 * t - 5 * t * t;
if (isUpControllerDied) {
postInvalidate();
}
}
});
freeDownController.addListener(new SimpleAnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
isBallFreeUp = true;
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
isAnimationShowing = false;
//迴圈第二次
startTotalAnimations();
}
});
//通過AnimatorSet集合開啟所有動畫
animatorSet = new AnimatorSet();
//設定動畫開啟的先後順序
animatorSet.play(downController).before(upController);
//監聽動畫的執行
animatorSet.addListener(new SimpleAnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
isAnimationShowing = true;
}
});
}
/**
* 開啟所有動畫
*/
public void startTotalAnimations() {
if (isAnimationShowing) {
return;
}
if (animatorSet.isRunning()) {
animatorSet.end();
animatorSet.cancel();
}
isBounced = false;
isBallFreeUp = false;
isUpControllerDied = false;
animatorSet.start();
}
/**
* 初始化自定義屬性
*
* @param context
* @param attrs
*/
private void initAttributes(Context context, AttributeSet attrs) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.LoadView);
mLineColor = array.getColor(R.styleable.LoadView_line_color, mLineColor);
mLineWidth = array.getDimensionPixelOffset(R.styleable.LoadView_line_width, mLineWidth);
mLineHeight = array.getDimensionPixelOffset(R.styleable.LoadView_line_height, mLineHeight);
mPointSize = array.getDimensionPixelOffset(R.styleable.LoadView_point_size, mPointSize);
mPointColor = array.getColor(R.styleable.LoadView_point_color, mPointColor);
array.recycle();
}
@Override
protected void onDraw(Canvas canvas) {
//1.繪製一條繩子
/**
* 一條繩子用左右兩部分的二階貝塞爾來繪製
*/
mPaint.setColor(mLineColor);
mPath.reset();
//起始點
mPath.moveTo(getWidth() / 2 - mLineWidth / 2, getHeight() / 2);
if (state == STATE_DOWN) {
//下墜
//左邊的貝塞爾
mPath.quadTo(
(float) (getWidth() / 2 - mLineWidth / 2 + mLineWidth * 0.375),
getHeight() / 2 + mDownDistance,
getWidth() / 2,
getHeight() / 2 + mDownDistance);
//右邊的貝塞爾
mPath.quadTo(
(float) (getWidth() / 2 + mLineWidth / 2 - mLineWidth * 0.375),
getHeight() / 2 + mDownDistance,
getWidth() / 2 + mLineWidth / 2,
getHeight() / 2);
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawPath(mPath, mPaint);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mPointColor);
canvas.drawCircle(getWidth() / 2, getHeight() / 2 + mDownDistance - mPointSize, mPointSize, mPaint);
} else if (state == STATE_UP) {
//向上彈
//繩子照樣繪製
//左邊的貝塞爾
mPath.quadTo(
(float) (getWidth() / 2 - mLineWidth / 2 + mLineWidth * 0.375),
getHeight() / 2 + (distance - mUpDistance),
getWidth() / 2,
getHeight() / 2 + (distance - mUpDistance));
//右邊的貝塞爾
mPath.quadTo(
(float) (getWidth() / 2 + mLineWidth / 2 - mLineWidth * 0.375),
getHeight() / 2 + (distance - mUpDistance),
getWidth() / 2 + mLineWidth / 2,
getHeight() / 2);
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawPath(mPath, mPaint);
//第三種狀態 自由落體
//2.繪製彈性小球
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mPointColor);
if (!isBounced) {
//正常上升
canvas.drawCircle(getWidth() / 2, getHeight() / 2 + (distance - mUpDistance) - mPointSize, mPointSize, mPaint);
} else {
//自由落體
canvas.drawCircle(getWidth() / 2, getHeight() / 2 - freeBallDistance - mPointSize, mPointSize, mPaint);
}
}
//3.繪製兩邊的小球 兩邊的固定點的圓
mPaint.setColor(mPointColor);
mPaint.setStyle(Paint.Style.FILL);
//繪製左邊小圓
canvas.drawCircle(getWidth() / 2 - mLineWidth / 2, getHeight() / 2, mPointSize, mPaint);
//繪製右邊小圓
canvas.drawCircle(getWidth() / 2 + mLineWidth / 2, getHeight() / 2, mPointSize, mPaint);
super.onDraw(canvas);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
//鎖定畫布
Canvas canvas = holder.lockCanvas();
//進行繪製
draw(canvas);
//解除鎖定
holder.unlockCanvasAndPost(canvas);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
}
/**
* Created by Administrator on 2017/12/23.
* 自定義加速器
*/
public class DampingInterpolator implements Interpolator {
@Override
public float getInterpolation(float input) {
return (float) (1 - Math.exp(-3 * input) * Math.cos(10 * input));
}
}
/**
* Created by Administrator on 2017/12/23.
實現AnimatorListener介面,在監聽動畫的時候可以建立SimpleAnimatorListener抽象類子類
根據需要去重寫相應的方法
*/
public abstract class SimpleAnimatorListener implements Animator.AnimatorListener {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
}
在LoadView類中提供了startTotalAnimations()方法,在對應的activity頁面中開啟動畫就ok了。
public class MainActivity extends AppCompatActivity {
private LoadView qp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
qp = (LoadView) findViewById(R.id.qp);
//開啟動畫
qp.startTotalAnimations();
}
}