之前有个项目,要求小气泡从A到沿着某一特定曲线平滑漂移到B点。下面是这个效果实现的一个demo,
本文聊聊这个效果是如何实现的。
可以参考网友的这篇博文,其他还有很多。
原理简述
public class Bubble {
/** 小球半径 */
private int radius;
/*启动延迟时间*/
int delays;
/** 小球x坐标 */
private float x;
/** 小球y坐标 */
private float y;
Bubble(int radius, int delays, float x, float y) {
this.radius = radius;
this.delays = delays;
this.x = x;
this.y = y;
}
public int getDelays() {
return delays;
}
public void setDelays(int delays) {
this.delays = delays;
}
public int getRadius() {
return radius;
}
public float getY() {
return y;
}
public float getX() {
return x;
}
public void setRadius(int radius) {
this.radius = radius;
}
public void setX(float x) {
this.x = x;
}
public void setY(float y) {
this.y = y;
}
public static class Builder {
/** 小球半径 */
int radius;
/*启动延迟时间*/
int delays;
/** 小球x坐标 */
float x;
/** 小球y坐标 */
float y;
public Builder radius(int radius) {
this.radius = radius;
return this;
}
public Builder delays(int delays) {
this.delays = delays;
return this;
}
public Builder x(float x) {
this.x = x;
return this;
}
public Builder y(float y) {
this.y = y;
return this;
}
public Bubble build() {
return new Bubble(radius, delays, x, y);
}
}
那么具体是如何实现曲线运动的?
我们在构造函数中对所需要的基本对象如:画笔,进行初始化。
paint = new Paint();
paint.setAlpha(200);
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
然后写一个小球的创造器,等动画开始之前使用。
private void createBubbles() {
Bubble bubble1 = new Bubble.Builder().radius(dip2px(mContext, 9))
.delays(0).x(startX).y(startY).build();
Bubble bubble2 = new Bubble.Builder().radius(dip2px(mContext, 7))
.delays(100).x(startX).y(startY).build();
Bubble bubble3 = new Bubble.Builder().radius(dip2px(mContext, 5))
.delays(150).x(startX).y(startY).build();
Bubble bubble4 = new Bubble.Builder().radius(dip2px(mContext, 4))
.delays(200).x(startX).y(startY).build();
bubbles.add(bubble1);
bubbles.add(bubble2);
bubbles.add(bubble3);
bubbles.add(bubble4);
}
创建小球需要的动画
private void createAnimation() {
isStoped = false;
final int BALL_SIZE = bubbles.size();
for (int i = 0; i < BALL_SIZE; i++) {
ValueAnimator xyAnimation = ValueAnimator.ofFloat(0f, 1f);
xyAnimation.setDuration(800+ i * 50);
xyAnimation.setRepeatCount(0);
xyAnimation.setStartDelay(bubbles.get(i).getDelays());
xyAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
xyAnimation.addUpdateListener(new BubbleAnimatorUpdateListener(i, bubbles.get(i)));
xyAnimation.start();
}
}
写小球动画的监听,在监听中调整小球的坐标并且通知ondraw绘制小球的坐标,形成曲线。这里小球的坐标计算我们用到了4阶的贝塞尔曲线。这里只是简单的套公式。
class BubbleAnimatorUpdateListener implements ValueAnimator.AnimatorUpdateListener {
private Bubble bubble;
private int index;
public BubbleAnimatorUpdateListener(int index, Bubble bubble) {
this.bubble = bubble;
this.index = index;
}
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float t = ((Float) valueAnimator.getAnimatedValue()).floatValue();
float Px0 = startX;
float Px1 = 0;
float Px2 = 0;
float Px3 = 0;
float Px4 = endX;
float Py0 = startY;
float Py1 = startY/2 + index;
float Py2 = startY/3 + index;
float Py3 = startY/4 + index;
float Py4 = endY;
if (index == 0) {
Px1 = startX + (endX - startX)/2;
Px2 = startX + (endX - startX)/3;
Px3 = endX + (endX - startX)/3;
} else if (index == 1) {
Px1 = startX + (endX - startX)/1;
Px2 = startX + (endX - startX)/3;
Px3 = endX + (endX - startX)/3;
} else if (index == 2) {
Px1 = startX - (endX - startX)/4;
Px2 = startX;
Px3 = endX + (endX - startX)/3;
} else {
Px1 = startX + (endX - startX)/1;
Px2 = startX + (endX - startX)/3;
Px3 = endX + (endX - startX)/3;
}
if (isStoped) {
valueAnimator.cancel();
bubble.setX(endX);
bubble.setY(endY);
} else {
bubble.setX((float) (Px0 * Math.pow((1 - t), 4) + 4 * Px1 * t * Math.pow(1 - t, 3) + 6 * Px2 * Math.pow(t, 2) * Math.pow(1 - t, 2) + 4 * Px3 * (1 - t) * Math.pow(t, 3) + Px4 * Math.pow(t,4)));
bubble.setY((float) (Py0 * Math.pow((1 - t), 4) + 4 * Py1 * t * Math.pow(1 - t, 3) + 6 * Py2 * Math.pow(t, 2) * Math.pow(1 - t, 2) + 4 * Py3 * (1 - t) * Math.pow(t, 3) + Py4 * Math.pow(t,4)));
}
if (1f == ((Float) valueAnimator.getAnimatedValue()).floatValue()) {
bubbles.remove(bubble);
if (bubbleViewListener != null && bubbles.size() == 3) {
bubbleViewListener.onEndListener();
}
}
invalidate();
}
}
根据坐标绘制小球
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (bubbles != null) {
for (Bubble bubble : bubbles) {
canvas.save();
android.util.Log.e("createAnimation", "bubble.getX() " + bubble.getX());
canvas.drawCircle(bubble.getX(), bubble.getY(),
bubble.getRadius(), paint);
canvas.restore();
}
}
}
小结:其实本文关于贝塞尔曲线只是套了公式,主要用了android的ValueAnimator 动画来控制坐标和动画速度,在ondraw方法中进行界面绘制。