Android实现跳动的小球加载动画效果
作者:daisy 发布时间:2022-01-24 19:43:28
标签:android,跳动,动画
先来看看效果图
跳动的小球做这个动画,需掌握:
1、属性动画
2、Path类、Canvas类
3、贝塞尔曲线
4、SurfaceView用法
5、自定义attr属性
6 、架构: 状态模式,控制器
7 、自由落体,抛物线等概念
不多说了,直接上码
1.DancingView.java
public class DancingView extends SurfaceView implements SurfaceHolder.Callback {
public static final int STATE_DOWN = 1;//向下状态
public static final int STATE_UP = 2;//向上状态
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) {
//进入自由落体状态
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;
//循环第二次
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) {
}
});
}
/**
* 启动动画,外部调用
*/
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);
/**************绘制绳子结束*************/
/**************绘制弹跳小球开始*************/
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mBallColor);
canvas.drawCircle(getWidth() / 2, getHeight() / 2 + mDownDistance - BALL_RADIUS, BALL_RADIUS, mPaint);
/**************绘制弹跳小球结束*************/
} 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);
/**************绘制绳子结束*************/
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>
注意:颜色、尺寸、参数可以自己测试,调整。
0
投稿
猜你喜欢
- Android getevent用法实例详解最近在测试设备按键的常用命令,感觉这些命令都有的,但就是不知道怎么查找。翻阅了几篇博
- springboot读取文件,打成jar包后访问不到最新开发出现一种情况,springboot打成jar包后读取不到文件,原因是打包之后,文
- 如果你发现在一个接口使用有如下定义方法: public String[] getParameters();那么你应该认
- Apache的POI项目可以用来处理MS Office文档,codeplex上还有一个它的.net版本。POI项目可创建和维护操作各种基于O
- 第一步:整合pom文件,在Spring Cloud中添加XXL-Job的依赖<!-- xxl-job-core --><d
- 最近有一个项目要用到年份周期,用于数据统计图表展示使用,当中用到年份周期,以及年份周期所在的日期范围。当初设想通过已知数据来换算年份周期,经
- 配置文件中设置通常在公司级别的项目中,我们可能会写多个application- dev/prod.yml ,然后我们通常会在applicat
- 运算符重载一直是一个很诡异事情,因为在写代码的时候,不知道某个运算符有没有被重载过。在 C++ 里面,运算符重载可以写在类的外面,当 int
- 本文实例形式展示了DevExpress实现GridControl根据列选中一行的方法,比较实用的功能,希望能对大家进行项目开发起到一定的借鉴
- 为什么说是常见问题整合呢,因为小编我就是Genymotion模板器最悲剧的使用者,该见过的问题,我基本都见过了,在此总结出这血的教训,望大家
- 本文实例为大家分享了C# GDI+实现时钟表盘的具体代码,供大家参考,具体内容如下一、设计如下图界面按键“打开时钟&am
- 这篇文章主要介绍了如何使用Jenkins编译并打包SpringCloud微服务目录,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有
- 目录前言解决方案前言我们在开发Spring应用时可能会不小心注入两个相同类型的Bean,比如实现了两个相同Service接口的类,示例伪代码
- synchronized关键字synchronized,我们谓之锁,主要用来给方法、代码块加锁。当某个方法或者代码块使用synchroniz
- 一、说明Boost.MPI 提供了 MPI 标准(消息传递接口)的接口。该标准简化了并发执行任务的程序的开发。您可以使用线程或通过共享内存或
- 本文实例讲述了java字符串相似度算法。分享给大家供大家参考。具体实现方法如下:public class Levenshtein {&nbs
- 一个Maprduce程序主要包括三部分:Mapper类、Reducer类、执行类。Maven项目下所需依赖<dependencies&
- 小背景:我们公司项目中的小脚本是一些工具类,比如常用的是MapUtil工具类的一些方法写公司的MapUtil工具类的方法要注意,方法名的命名
- 本文实例讲述了C#不重复输出一个数组中所有元素的方法。分享给大家供大家参考。具体如下:1.算法描述0)输入合法性校验1)建立临时数组:与原数
- 一、直接执行SQL查询:1、mappers文件节选<resultMap id="AcModelResultMap"