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>
注意:颜色、尺寸、参数可以自己测试,调整。


猜你喜欢
- 本文为大家分享Android自定义Spinner适配器的相关知识点,供大家参考,具体内容如下一、大致效果二.关键代码在注释中讲重点吧。 (1
- 读XMLXmlDocument xd = new XmlDocument(); string fi
- 引言layout: postcategories: Javatitle: 一文带你了解 Spring 的@Enablexxx 注解tagli
- 一、让中央控制器动态加载存储子控制器上期回顾,我们说明了自定义MVC工作原理,其中,中央控制器起到了接收浏览器请求,找到对应的处理人的一个作
- [LeetCode] 159. Longest Substring with At Most Two Distinct Characters
- 简介JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案。JSON Web Token 入门教程 这篇文章可以帮你了解
- Java中使用也比较简单:1. 编译正则表达式的字面值得到对应的模式Pattern对象;2. 创建匹配给定输入与此模式的匹配器Matcher
- 最近一年的项目都是在使用Mybatis-plus,感觉挺好用的,也没遇到很多问题,但是在最近项目上线之后,遇到了一些新的需要,在进行新版本开
- 目录背景介绍项目介绍需要知识点启动项目项目示范核心讲解核心原理功能分析分块上传秒传功能断点续传总结参考文献背景介绍 Breakpoint-
- Android Studio从3.0版本新增了许多功能,当然首当其冲就是从3.0版本新增了对 Kotlin 开发语言的支持,除此之外还有其他
- 1 异常异常的体系• ThrowableError通常出现重大问题如:运行的类不存在或者内存溢出等。不编写针对代码对其处理Exception
- 本文实例讲述了Java基于socket实现的客户端和服务端通信功能。分享给大家供大家参考,具体如下:以下代码参考马士兵的聊天项目,先运行Ch
- Spring是一个非常流行的Java Web开发框架,它提供了强大的依赖注入、面向切面编程、声明式事务管理等功能,为开发者提供了高效、快速地
- Android短信验证码功能,供大家参考,具体内容如下1、参考资料Mob网站:http://www.mob.com/Mob在Github上的
- 一、函数和变量的多文件问题.h: 头文件,一般包含函数声明,变量声明,宏定义,头文件等内容(header).c : 源文件,一般包含函数实现
- 本文实例讲述了Android中资源文件用法。分享给大家供大家参考,具体如下:一、XML文件间资源文件的使用引用格式:attribute=&q
- 一、注解(annotations)列表 @SpringBootApplication:包含了@ComponentScan、@Configur
- c#里面封装了几乎所有我们可以想到的和我们没有想到的类,流是读取文件的一般手段,那么你真的会用它读取文件中的数据了么?真的能读完全么?通常我
- 本文实例为大家分享了Android仿美团下拉菜单的实现代码,分类进行选择,供大家参考,具体内容如下效果图操作平台AS2.0第三方框架:but
- 目前网上流行着很多对“时间对话框TimePickerDialog”的讲解文章,但感觉都不是很详细。所以浣熊在这里详细对该方面的知识进行介绍,