版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wangwo1991/article/details/78882472
实现上面的效果涉及到左右两个小圆点、中间小圆点、线条的绘制,同时涉及到属性动画及二阶贝塞尔曲线的使用。
A点和B点的两个小圆点比较好绘制,确定相应的坐标后,调用canvas.drawCircle()方法进行绘制;
A点的X就是:getWidth(自定义控件的宽度)/2-mLineWidth(A点到B点的长度,即线条的长度)/2;
C点的X就是:getWidth(自定义控件的宽度)/2+mLineWidth(A点到B点的长度,即线条的长度)/2;
A点和C点的Y是一样的都是:getHeight()/2
接下来就是绘制线条和中间的小球,将小球的状态定义为3种:下坠,上升及自由落体,在小球下坠、上升或者自由落体时,调用mPath.moveTo()和mPath.quadTo()进行左右贝塞尔曲线的绘制,A点和C点的坐标在绘制左右两个小点的时候就已经得到了,
B点的X坐标是:getWidth(自定义控件的宽度)/2+mLineWidth(A点到B点的长度,即线条的长度)/2;
Y的坐标在上升或者下坠过程中就有区别:下坠Y坐标:getHeight() / 2 + mDownDistance(下坠的距离);上升或者自由落地Y坐标:getHeight() / 2 + (distance(定义的一个距离常量) - mUpDistance(上升的距离))
D和E点的坐标可以通过调试后获取到最佳的坐标点,相应的三个点的坐标值获取后,就可以绘制相应的贝塞尔曲线了;
剩下就只有中间小球的坐标位置了,不管下坠、上升或者自由落体,X对应的坐标都是getWidth(自定义控件的宽度)/2;
但是对应的Y的坐标就会有区别:
下坠:getHeight() / 2 + mDownDistance(下坠的距离) - mPointSize(小球的半径)
正常上升:getHeight() / 2 + (distance - mUpDistance) - mPointSize
自由落地:getHeight() / 2 - freeBallDistance(自由落体的距离) - mPointSize
获取到对应的坐标点后,调用onDraw()方法进行绘制,小球的上下运动是通过ValueAnimator来控制的;
实现主要代码(代码中有相应的注释):
public class LoadView extends SurfaceView implements SurfaceHolder.Callback {
private static final int STATE_DOWN = 1;
private static final int STATE_UP = 2;
private Paint mPaint;
private Path mPath;
//线的颜色
private int mLineColor = Color.WHITE;
//点的颜色
private int mPointColor = Color.BLACK;
//点的大小
private int mPointSize = 10;
//线的宽度
private int mLineWidth = 200;
//线的高度
private int mLineHeight = 2;
//往下走的距离
private float mDownDistance;
private static final int distance=50;
//往上走的距离
private float mUpDistance;
private float freeBallDistance;
//向下运动
private ValueAnimator downController;
//向上运动
private ValueAnimator upController;
//自由落体
private ValueAnimator freeDownController;
//动画集合
private AnimatorSet animatorSet;
private int state;
//是否向上弹起
private boolean isBounced = false;
private boolean isBallFreeUp = false;
private boolean isUpControllerDied = false;
private boolean isAnimationShowing = false;
public LoadView(Context context) {
this(context, null);
}
public LoadView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LoadView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
/**
* 初始化
*
* @param context
* @param 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);//圆形线帽
//初始化path
mPath = new Path();
getHolder().addCallback(this);
initController();
}
private void initController() {
downController = ValueAnimator.ofFloat(0, 1);
//设置动画持续的时长
downController.setDuration(500);
//设置差值器
downController.setInterpolator(new DecelerateInterpolator());
//监听动画执行
downController.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mDownDistance = distance * (float) animation.getAnimatedValue();
//进行重绘
postInvalidate();
}
});
downController.addListener(new SimpleAnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
state = STATE_DOWN;
}
});
upController = ValueAnimator.ofFloat(0, 1);
upController.setDuration(900);
upController.setInterpolator(new DampingInterpolator());
upController.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mUpDistance = distance * (float) animation.getAnimatedValue();
if (mUpDistance >= distance) {
//进入自由落体状态
isBounced = true;
if (!freeDownController.isRunning() &&
!freeDownController.isStarted() &&
!isBallFreeUp) {
freeDownController.start();
}
}
postInvalidate();
}
});
upController.addListener(new SimpleAnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
state = STATE_UP;
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
isUpControllerDied=true;
}
});
freeDownController = ValueAnimator.ofFloat(0, 6.8f);
freeDownController.setDuration(600);
freeDownController.setInterpolator(new DecelerateInterpolator());
freeDownController.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//一个公式解决上升减速和下降加速
float t = (float) animation.getAnimatedValue();
freeBallDistance = 34 * t - 5 * t * t;
if (isUpControllerDied) {
postInvalidate();
}
}
});
freeDownController.addListener(new SimpleAnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
isBallFreeUp = true;
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
isAnimationShowing = false;
//循环第二次
startTotalAnimations();
}
});
//通过AnimatorSet集合开启所有动画
animatorSet = new AnimatorSet();
//设置动画开启的先后顺序
animatorSet.play(downController).before(upController);
//监听动画的执行
animatorSet.addListener(new SimpleAnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
isAnimationShowing = true;
}
});
}
/**
* 开启所有动画
*/
public void startTotalAnimations() {
if (isAnimationShowing) {
return;
}
if (animatorSet.isRunning()) {
animatorSet.end();
animatorSet.cancel();
}
isBounced = false;
isBallFreeUp = false;
isUpControllerDied = false;
animatorSet.start();
}
/**
* 初始化自定义属性
*
* @param context
* @param attrs
*/
private void initAttributes(Context context, AttributeSet attrs) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.LoadView);
mLineColor = array.getColor(R.styleable.LoadView_line_color, mLineColor);
mLineWidth = array.getDimensionPixelOffset(R.styleable.LoadView_line_width, mLineWidth);
mLineHeight = array.getDimensionPixelOffset(R.styleable.LoadView_line_height, mLineHeight);
mPointSize = array.getDimensionPixelOffset(R.styleable.LoadView_point_size, mPointSize);
mPointColor = array.getColor(R.styleable.LoadView_point_color, mPointColor);
array.recycle();
}
@Override
protected void onDraw(Canvas canvas) {
//1.绘制一条绳子
/**
* 一条绳子用左右两部分的二阶贝塞尔来绘制
*/
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(mPointColor);
canvas.drawCircle(getWidth() / 2, getHeight() / 2 + mDownDistance - mPointSize, mPointSize, mPaint);
} else if (state == STATE_UP) {
//向上弹
//绳子照样绘制
//左边的贝塞尔
mPath.quadTo(
(float) (getWidth() / 2 - mLineWidth / 2 + mLineWidth * 0.375),
getHeight() / 2 + (distance - mUpDistance),
getWidth() / 2,
getHeight() / 2 + (distance - mUpDistance));
//右边的贝塞尔
mPath.quadTo(
(float) (getWidth() / 2 + mLineWidth / 2 - mLineWidth * 0.375),
getHeight() / 2 + (distance - mUpDistance),
getWidth() / 2 + mLineWidth / 2,
getHeight() / 2);
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawPath(mPath, mPaint);
//第三种状态 自由落体
//2.绘制弹性小球
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mPointColor);
if (!isBounced) {
//正常上升
canvas.drawCircle(getWidth() / 2, getHeight() / 2 + (distance - mUpDistance) - mPointSize, mPointSize, mPaint);
} else {
//自由落体
canvas.drawCircle(getWidth() / 2, getHeight() / 2 - freeBallDistance - mPointSize, mPointSize, mPaint);
}
}
//3.绘制两边的小球 两边的固定点的圆
mPaint.setColor(mPointColor);
mPaint.setStyle(Paint.Style.FILL);
//绘制左边小圆
canvas.drawCircle(getWidth() / 2 - mLineWidth / 2, getHeight() / 2, mPointSize, mPaint);
//绘制右边小圆
canvas.drawCircle(getWidth() / 2 + mLineWidth / 2, getHeight() / 2, mPointSize, mPaint);
super.onDraw(canvas);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
//锁定画布
Canvas canvas = holder.lockCanvas();
//进行绘制
draw(canvas);
//解除锁定
holder.unlockCanvasAndPost(canvas);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
}
/**
* Created by Administrator on 2017/12/23.
* 自定义加速器
*/
public class DampingInterpolator implements Interpolator {
@Override
public float getInterpolation(float input) {
return (float) (1 - Math.exp(-3 * input) * Math.cos(10 * input));
}
}
/**
* Created by Administrator on 2017/12/23.
实现AnimatorListener接口,在监听动画的时候可以创建SimpleAnimatorListener抽象类子类
根据需要去重写相应的方法
*/
public abstract class SimpleAnimatorListener implements Animator.AnimatorListener {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
}
在LoadView类中提供了startTotalAnimations()方法,在对应的activity页面中开启动画就ok了。
public class MainActivity extends AppCompatActivity {
private LoadView qp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
qp = (LoadView) findViewById(R.id.qp);
//开启动画
qp.startTotalAnimations();
}
}