Android實現(xiàn)跳動的小球加載動畫效果
先來看看效果圖

跳動的小球做這個動畫,需掌握:
1、屬性動畫
2、Path類、Canvas類
3、貝塞爾曲線
4、SurfaceView用法
5、自定義attr屬性
6 、架構(gòu): 狀態(tài)模式,控制器
7 、自由落體,拋物線等概念
不多說了,直接上碼
1.DancingView.java
public class DancingView extends SurfaceView implements SurfaceHolder.Callback {
public static final int STATE_DOWN = 1;//向下狀態(tài)
public static final int STATE_UP = 2;//向上狀態(tài)
public static final int DEFAULT_POINT_RADIUS = 10;
public static final int DEFAULT_BALL_RADIUS = 13;
public static final int DEFAULT_LINE_WIDTH = 200;
public static final int DEFAULT_LINE_HEIGHT = 2;
public static final int DEFAULT_LINE_COLOR = Color.parseColor("#FF9800");
public static final int DEFAULT_POINT_COLOR = Color.parseColor("#9C27B0");
public static final int DEFAULT_BALL_COLOR = Color.parseColor("#FF4081");
public static final int DEFAULT_DOWN_DURATION = 600;//ms
public static final int DEFAULT_UP_DURATION = 600;//ms
public static final int DEFAULT_FREEDOWN_DURATION = 1000;//ms
public static final int MAX_OFFSET_Y = 50;//水平下降最大偏移距離
public int PONIT_RADIUS = DEFAULT_POINT_RADIUS;//小球半徑
public int BALL_RADIUS = DEFAULT_BALL_RADIUS;//小球半徑
private Paint mPaint;
private Path mPath;
private int mLineColor;
private int mPonitColor;
private int mBallColor;
private int mLineWidth;
private int mLineHeight;
private float mDownDistance;
private float mUpDistance;
private float freeBallDistance;
private ValueAnimator mDownController;//下落控制器
private ValueAnimator mUpController;//上彈控制器
private ValueAnimator mFreeDownController;//自由落體控制器
private AnimatorSet animatorSet;
private int state;
private boolean ismUpControllerDied = false;
private boolean isAnimationShowing = false;
private boolean isBounced = false;
private boolean isBallFreeUp = false;
public DancingView(Context context) {
super(context);
init(context, null);
}
public DancingView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public DancingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, 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);
mPath = new Path();
getHolder().addCallback(this);
initController();
}
private void initAttributes(Context context, AttributeSet attrs) {
TypedArray typeArray = context.obtainStyledAttributes(attrs, R.styleable.DancingView);
mLineColor = typeArray.getColor(R.styleable.DancingView_lineColor, DEFAULT_LINE_COLOR);
mLineWidth = typeArray.getDimensionPixelOffset(R.styleable.DancingView_lineWidth, DEFAULT_LINE_WIDTH);
mLineHeight = typeArray.getDimensionPixelOffset(R.styleable.DancingView_lineHeight, DEFAULT_LINE_HEIGHT);
mPonitColor = typeArray.getColor(R.styleable.DancingView_pointColor, DEFAULT_POINT_COLOR);
mBallColor = typeArray.getColor(R.styleable.DancingView_ballColor, DEFAULT_BALL_COLOR);
typeArray.recycle();
}
private void initController() {
mDownController = ValueAnimator.ofFloat(0, 1);
mDownController.setDuration(DEFAULT_DOWN_DURATION);
mDownController.setInterpolator(new DecelerateInterpolator());
mDownController.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mDownDistance = MAX_OFFSET_Y * (float) animation.getAnimatedValue();
postInvalidate();
}
});
mDownController.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
state = STATE_DOWN;
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
mUpController = ValueAnimator.ofFloat(0, 1);
mUpController.setDuration(DEFAULT_UP_DURATION);
mUpController.setInterpolator(new DancingInterpolator());
mUpController.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mUpDistance = MAX_OFFSET_Y * (float) animation.getAnimatedValue();
if (mUpDistance >= MAX_OFFSET_Y) {
//進入自由落體狀態(tài)
isBounced = true;
if (!mFreeDownController.isRunning() && !mFreeDownController.isStarted() && !isBallFreeUp) {
mFreeDownController.start();
}
}
postInvalidate();
}
});
mUpController.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
state = STATE_UP;
}
@Override
public void onAnimationEnd(Animator animation) {
ismUpControllerDied = true;
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
mFreeDownController = ValueAnimator.ofFloat(0, 8f);
mFreeDownController.setDuration(DEFAULT_FREEDOWN_DURATION);
mFreeDownController.setInterpolator(new DecelerateInterpolator());
mFreeDownController.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//該公式解決上升減速 和 下降加速
float t = (float) animation.getAnimatedValue();
freeBallDistance = 40 * t - 5 * t * t;
if (ismUpControllerDied) {//往上拋,到臨界點
postInvalidate();
}
}
});
mFreeDownController.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
isBallFreeUp = true;
}
@Override
public void onAnimationEnd(Animator animation) {
isAnimationShowing = false;
//循環(huán)第二次
startAnimations();
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
animatorSet = new AnimatorSet();
animatorSet.play(mDownController).before(mUpController);
animatorSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
isAnimationShowing = true;
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
}
/**
* 啟動動畫,外部調(diào)用
*/
public void startAnimations() {
if (isAnimationShowing) {
return;
}
if (animatorSet.isRunning()) {
animatorSet.end();
animatorSet.cancel();
}
isBounced = false;
isBallFreeUp = false;
ismUpControllerDied = false;
animatorSet.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 一條繩子用左右兩部分的二階貝塞爾曲線組成
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);
/**************繪制繩子結(jié)束*************/
/**************繪制彈跳小球開始*************/
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mBallColor);
canvas.drawCircle(getWidth() / 2, getHeight() / 2 + mDownDistance - BALL_RADIUS, BALL_RADIUS, mPaint);
/**************繪制彈跳小球結(jié)束*************/
} else if (state == STATE_UP) { //向上彈
/**************繪制繩子開始*************/
//左部分的貝塞爾
mPath.quadTo((float) (getWidth() / 2 - mLineWidth / 2 + mLineWidth * 0.375), getHeight() / 2 + 50 - mUpDistance,
getWidth() / 2,
getHeight() / 2 + (50 - mUpDistance));
//右部分的貝塞爾
mPath.quadTo((float) (getWidth() / 2 + mLineWidth / 2 - mLineWidth * 0.375), getHeight() / 2 + 50 - mUpDistance,
getWidth() / 2 + mLineWidth / 2,
getHeight() / 2);
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawPath(mPath, mPaint);
/**************繪制繩子結(jié)束*************/
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mBallColor);
//彈性小球,自由落體
if (!isBounced) {
//上升
canvas.drawCircle(getWidth() / 2, getHeight() / 2 + (MAX_OFFSET_Y - mUpDistance) - BALL_RADIUS, BALL_RADIUS, mPaint);
} else {
//自由落體
canvas.drawCircle(getWidth() / 2, getHeight() / 2 - freeBallDistance - BALL_RADIUS, BALL_RADIUS, mPaint);
}
}
mPaint.setColor(mPonitColor);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(getWidth() / 2 - mLineWidth / 2, getHeight() / 2, PONIT_RADIUS, mPaint);
canvas.drawCircle(getWidth() / 2 + mLineWidth / 2, getHeight() / 2, PONIT_RADIUS, mPaint);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
Canvas canvas = holder.lockCanvas();//鎖定整個SurfaceView對象,獲取該Surface上的Canvas.
draw(canvas);
holder.unlockCanvasAndPost(canvas);//釋放畫布,提交修改
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
}
2.DancingInterpolator.java
public class DancingInterpolator implements Interpolator {
@Override
public float getInterpolation(float input) {
return (float) (1 - Math.exp(-3 * input) * Math.cos(10 * input));
}
}
3.自定義屬性 styles.xml
<declare-styleable name="DancingView">
<attr name="lineWidth" format="dimension" />
<attr name="lineHeight" format="dimension" />
<attr name="pointColor" format="reference|color" />
<attr name="lineColor" format="reference|color" />
<attr name="ballColor" format="reference|color" />
</declare-styleable>
注意:顏色、尺寸、參數(shù)可以自己測試,調(diào)整。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)和工作能有所幫助哦。
相關(guān)文章
Android 詳解沉浸式狀態(tài)欄的實現(xiàn)流程
沉浸式就是要給用戶提供完全沉浸的體驗,使用戶有一種置身于虛擬世界之中的感覺。沉浸式模式就是整個屏幕中顯示都是應(yīng)用的內(nèi)容,沒有狀態(tài)欄也沒有導(dǎo)航欄,用戶不會被一些系統(tǒng)的界面元素所打擾,讓我們來實現(xiàn)下網(wǎng)上傳的沸沸揚揚的安卓沉浸式狀態(tài)欄2021-11-11
Android開發(fā)之ProgressDialog進度對話框用法示例
這篇文章主要介紹了Android開發(fā)之ProgressDialog進度對話框用法,簡單介紹了ProgressDialog進度對話框常見函數(shù)功能,并結(jié)合實例形式分析了ProgressDialog組件創(chuàng)建及使用進度對話框相關(guān)操作技巧,需要的朋友可以參考下2019-03-03
Android開發(fā)手冊Chip監(jiān)聽及ChipGroup監(jiān)聽
這篇文章主要為大家介紹了Android開發(fā)手冊Chip監(jiān)聽及ChipGroup監(jiān)聽,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06
Android GridView不改變背景色實現(xiàn)網(wǎng)格線效果
這篇文章主要介紹了Android GridView不改變背景色實現(xiàn)網(wǎng)格線效果,需要的朋友可以參考下2016-03-03
Android6.0開發(fā)中屏幕旋轉(zhuǎn)原理與流程分析
這篇文章主要介紹了Android6.0開發(fā)中屏幕旋轉(zhuǎn)原理與流程,結(jié)合實例形式詳細(xì)分析了Android6.0屏幕旋轉(zhuǎn)的原理與相關(guān)實現(xiàn)流程,并附帶了Android動態(tài)開啟與禁用屏幕旋轉(zhuǎn)的實現(xiàn)方法,需要的朋友可以參考下2017-11-11

