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,跳动,动画
0
投稿

猜你喜欢

  • 关于Java的Condition接口最佳理解方式

    2021-05-28 19:06:35
  • Android ListView的Item点击效果的定制

    2023-06-21 11:46:01
  • Android检查手机网络状态及网络类型的方法

    2023-12-03 19:35:49
  • C#中ExecuteNonQuery()返回值注意点分析

    2023-04-05 03:40:14
  • 通过openOffice将office文件转成pdf

    2021-12-29 21:35:32
  • IDEA高效使用设置指南

    2023-10-30 01:39:37
  • java实现图片反色处理示例

    2022-07-09 20:53:34
  • springboot访问静态资源遇到的坑及解决

    2022-11-06 14:45:11
  • Android中WebView常见问题及解决方案汇总

    2021-06-26 07:23:02
  • Kotlin 接口与 Java8 新特性接口详解

    2023-09-10 01:10:45
  • java substring 截取字符串的方法

    2023-02-12 17:21:19
  • Java DecimalFormat 保留小数位及四舍五入的陷阱介绍

    2023-11-09 04:49:33
  • android中实现指针滑动的动态效果方法

    2023-10-07 15:54:44
  • 使用controller传boolean形式值

    2023-11-28 23:05:33
  • C#中参数个数可变的方法实例分析

    2022-12-16 20:02:32
  • Gson之toJson和fromJson方法的具体使用

    2021-07-20 16:28:47
  • 实现一个Android锁屏App功能的难点总结

    2022-07-21 03:03:19
  • JAVA 实现磁盘文件加解密操作的示例代码

    2023-11-15 00:13:06
  • c#操作Redis的5种基本类型汇总

    2021-06-03 03:24:07
  • 详解SpringBoot使用RedisTemplate操作Redis的5种数据类型

    2022-12-03 07:35:29
  • asp之家 软件编程 m.aspxhome.com