自定义View合辑（8）-跳跃的小球（贝塞尔曲线）

2019.05.11 22:49 409浏览

GitHub 地址：https://github.com/leavesC/CustomView

一、思路解析

• 绘制一条水平线
• 在最高点绘制一个红色小球，X坐标居于水平线中间
• 通过 ValueAnimator 提供的加速插值器 AccelerateInterpolator 来逐渐增大小球的 Y 坐标，使之加速下落
• 当小球触碰到水平线的同时，通过改变贝塞尔曲线的控制点坐标，使得水平线和小球一直保持接触状态，即绘制出一条符合条件的曲线
• 当小球落到最低点时，通过减速插值器 DecelerateInterpolator 来逐渐减小小球的 Y 坐标，使之减速上升
• 当小球被反弹超出水平线一定高度内，水平线依然和小球保持接触
• 当小球离开水平线后，改变贝塞尔曲线的控制点来绘制出水平线的上下回弹效果

二、代码解析

``````    private static class Point {

private float x;

private float y;

}

//小球
private Point ballPoint;

//贝塞尔曲线控制点
private Point controlPoint;
``````

``````    private float lineY;

private float lineXLeft;

private float lineXRight;

//小球最高点Y坐标
private float pointYMin;

@Override
protected void onSizeChanged(int contentWidth, int contentHeight, int oldW, int oldH) {
super.onSizeChanged(contentWidth, contentHeight, oldW, oldH);
lineY = contentHeight * 0.5f;
lineXLeft = contentWidth * 0.15f;
lineXRight = contentWidth * 0.85f;

//小球最低点Y坐标
float pointYMax = contentHeight * 0.55f;
pointYMin = contentHeight * 0.22f;

ballPoint.x = contentWidth * 0.5F;
ballPoint.y = pointYMin;

controlPoint.x = ballPoint.x;

long speed = 1800;
downAnimator.setFloatValues(pointYMin, pointYMax);
upAnimator.setFloatValues(pointYMax, pointYMin);
downAnimator.setDuration(speed);
upAnimator.setDuration((long) (0.8 * speed));
start();
}
``````

``````  private void initAnimator() {
downAnimator = new ValueAnimator();
//加速下降
downAnimator.setInterpolator(new AccelerateInterpolator());
@Override
public void onAnimationUpdate(ValueAnimator animation) {
ballPoint.y = (float) animation.getAnimatedValue();
if (ballPoint.y + ballPoint.radius <= lineY) {
controlPoint.y = lineY;
} else {
controlPoint.y = lineY + 2 * (ballPoint.y + ballPoint.radius - lineY);
}
invalidate();
}
});
@Override
public void onAnimationEnd(Animator animation) {
startUpAnimator();
}
});

upAnimator = new ValueAnimator();
//减速上升
upAnimator.setInterpolator(new DecelerateInterpolator());
@Override
public void onAnimationUpdate(ValueAnimator animation) {
ballPoint.y = (float) animation.getAnimatedValue();
if (ballPoint.y + ballPoint.radius >= lineY) { //还处于水平线以下
controlPoint.y = lineY + 2 * (ballPoint.y + ballPoint.radius - lineY);
} else {
//小球总的要上升的距离
float tempY = lineY - pointYMin;
//小球最低点距离水平线的距离，即小球已上升的距离
float distance = lineY - ballPoint.y - ballPoint.radius;
//上升比例
float percentage = distance / tempY;
if (percentage <= 0.2) {  //线从水平线升高到最高点
controlPoint.y = lineY + 2 * (ballPoint.y + ballPoint.radius - lineY);
} else if (percentage <= 0.28) { //线从最高点降落到水平线
controlPoint.y = lineY - (distance - tempY * 0.2f);
} else if (percentage <= 0.34) { //线从水平线降落到最低点
controlPoint.y = lineY + (distance - tempY * 0.28f);
} else if (percentage <= 0.39) { //线从最低点升高到水平线
controlPoint.y = lineY - (distance - tempY * 0.34f);
} else {
controlPoint.y = lineY;
}
}
invalidate();
}
});
@Override
public void onAnimationEnd(Animator animation) {
startDownAnimator();
}
});
}
``````

``````    private Path path = new Path();

@Override
protected void onDraw(Canvas canvas) {
paint.setColor(Color.WHITE);
paint.setStrokeWidth(8f);

path.reset();
path.moveTo(lineXLeft, lineY);
paint.setStyle(Paint.Style.STROKE);
canvas.drawPath(path, paint);

paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(lineXLeft, lineY, 16, paint);
canvas.drawCircle(lineXRight, lineY, 16, paint);

paint.setColor(Color.parseColor("#f7584d"));
paint.setStrokeWidth(0f);
}
``````

``````/**
* 作者：leavesC
* 时间：2019/5/1 23:04
* 描述：
* GitHub：https://github.com/leavesC
* Blog：https://www.jianshu.com/u/9df45b87cfdf
*/
public class PointBeatView extends BaseView {

private static class Point {

private float x;

private float y;

}

//小球
private Point ballPoint;

//贝塞尔曲线控制点
private Point controlPoint;

private ValueAnimator downAnimator;

private ValueAnimator upAnimator;

private float lineY;

private float lineXLeft;

private float lineXRight;

//小球最高点Y坐标
private float pointYMin;

private Paint paint;

public PointBeatView(Context context) {
this(context, null);
}

public PointBeatView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}

public PointBeatView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
ballPoint = new Point();
controlPoint = new Point();
initPaint();
initAnimator();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = getSize(widthMeasureSpec, getResources().getDisplayMetrics().widthPixels);
int height = getSize(heightMeasureSpec, getResources().getDisplayMetrics().heightPixels);
setMeasuredDimension(width, height);
}

private void initPaint() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setDither(true);
}

@Override
protected void onSizeChanged(int contentWidth, int contentHeight, int oldW, int oldH) {
super.onSizeChanged(contentWidth, contentHeight, oldW, oldH);
lineY = contentHeight * 0.5f;
lineXLeft = contentWidth * 0.15f;
lineXRight = contentWidth * 0.85f;

//小球最低点Y坐标
float pointYMax = contentHeight * 0.55f;
pointYMin = contentHeight * 0.22f;

ballPoint.x = contentWidth * 0.5F;
ballPoint.y = pointYMin;

controlPoint.x = ballPoint.x;

long speed = 1800;
downAnimator.setFloatValues(pointYMin, pointYMax);
upAnimator.setFloatValues(pointYMax, pointYMin);
downAnimator.setDuration(speed);
upAnimator.setDuration((long) (0.8 * speed));
start();
}

private Path path = new Path();

@Override
protected void onDraw(Canvas canvas) {
paint.setColor(Color.WHITE);
paint.setStrokeWidth(8f);

path.reset();
path.moveTo(lineXLeft, lineY);
paint.setStyle(Paint.Style.STROKE);
canvas.drawPath(path, paint);

paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(lineXLeft, lineY, 16, paint);
canvas.drawCircle(lineXRight, lineY, 16, paint);

paint.setColor(Color.parseColor("#f7584d"));
paint.setStrokeWidth(0f);
}

private void initAnimator() {
downAnimator = new ValueAnimator();
//加速下降
downAnimator.setInterpolator(new AccelerateInterpolator());
@Override
public void onAnimationUpdate(ValueAnimator animation) {
ballPoint.y = (float) animation.getAnimatedValue();
if (ballPoint.y + ballPoint.radius <= lineY) {
controlPoint.y = lineY;
} else {
controlPoint.y = lineY + 2 * (ballPoint.y + ballPoint.radius - lineY);
}
invalidate();
}
});
@Override
public void onAnimationEnd(Animator animation) {
startUpAnimator();
}
});

upAnimator = new ValueAnimator();
//减速上升
upAnimator.setInterpolator(new DecelerateInterpolator());
@Override
public void onAnimationUpdate(ValueAnimator animation) {
ballPoint.y = (float) animation.getAnimatedValue();
if (ballPoint.y + ballPoint.radius >= lineY) { //还处于水平线以下
controlPoint.y = lineY + 2 * (ballPoint.y + ballPoint.radius - lineY);
} else {
//小球总的要上升的距离
float tempY = lineY - pointYMin;
//小球最低点距离水平线的距离，即小球已上升的距离
float distance = lineY - ballPoint.y - ballPoint.radius;
//上升比例
float percentage = distance / tempY;
if (percentage <= 0.2) {  //线从水平线升高到最高点
controlPoint.y = lineY + 2 * (ballPoint.y + ballPoint.radius - lineY);
} else if (percentage <= 0.28) { //线从最高点降落到水平线
controlPoint.y = lineY - (distance - tempY * 0.2f);
} else if (percentage <= 0.34) { //线从水平线降落到最低点
controlPoint.y = lineY + (distance - tempY * 0.28f);
} else if (percentage <= 0.39) { //线从最低点升高到水平线
controlPoint.y = lineY - (distance - tempY * 0.34f);
} else {
controlPoint.y = lineY;
}
}
invalidate();
}
});
@Override
public void onAnimationEnd(Animator animation) {
startDownAnimator();
}
});
}

@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
stop();
}

@Override
protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
switch (visibility) {
case View.VISIBLE: {
start();
break;
}
case View.INVISIBLE:
case View.GONE: {
stop();
break;
}
}
Log.e(TAG, "onVisibilityChanged: " + visibility);
}

public void start() {
startDownAnimator();
}

public void stop() {
stopDownAnimator();
stopUpAnimator();
}

private void startDownAnimator() {
if (downAnimator != null && downAnimator.getValues() != null && downAnimator.getValues().length > 0 && !downAnimator.isRunning()) {
downAnimator.start();
}
}

private void stopDownAnimator() {
if (downAnimator != null && downAnimator.isRunning()) {
downAnimator.cancel();
}
}

private void startUpAnimator() {
if (upAnimator != null && upAnimator.getValues() != null && upAnimator.getValues().length > 0 && !upAnimator.isRunning()) {
upAnimator.start();
}
}

private void stopUpAnimator() {
if (upAnimator != null && upAnimator.isRunning()) {
upAnimator.cancel();
}
}

}
``````

0人点赞

• 推荐
• 评论
• 收藏
• 共同学习，写下你的评论

0/150