Android 中有各式各样的加载动画,大家多多少少都见过,比如用过美团客户端的用户对美团那个加载小人的动画印象很深刻,一个可爱的小人在那拼命的跑。这样的动画实现其实还有很多,今天这里就来实现一个跳动的小球效果。有需要的可以参考借鉴。
Android 是一种基于 Linux 的自由及开放源代码的操作系统,主要使用于移动设备,如智能手机和平板电脑,由 Google 公司和开放手机联盟领导及开发。尚未有统一中文名称,中国大陆地区较多人使用 "安卓" 或 "安致"。
先来看看效果图
跳动的小球做这个动画, 需掌握:
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>
注意:颜色、尺寸、参数可以自己测试,调整。
来源: http://www.phperz.com/article/17/0321/291568.html