Android贝兹曲线之抛物线动画

本文记录的是用Android属性动画结合贝兹曲线来实现类似抛物线的动画效果
下图为效果图

在我们写码代码之前先来分析一下实现这样的一个效果需要那些步骤,其实完成一个功能,我认为最大部分的时间应该是在分析、拆解功能,最后的代码只是水到渠成的事情,不要一上来就撸代码,结果头脑是懵的,这样效率非常低。

这样的一个效果可以拆分为两部分水平和垂直部分:

左上角的红球在水平方向作匀速运动,垂直方向的加速运动,如果水平和垂直方向都匀速运动的话就成两点之间的直线运动了,这其实就是抛物线的原理。

所以映射到我们的代码中的解释就是,左上角红球x轴匀速平移到底部红球的x轴位置,y轴加速平移到底部红球的y轴位置,这样就实现了左上角红球作抛物线运动到底部红球的位置的功能

上面提到了一个概念就是变化速度(匀速、加速),代码中怎么去实现这样的一个功能呢,对,没错,就是插值器,所以问题都是这样解决的,把一个大模块拆解成一个个的小模块然后一个个解决,化难为易。我理解的插值器其实就是属性变化的速率,其中根据速率改变的规律不同生成不一样的插值器,比如线性、非线性插值器等。

分析完了之后就是用代码去实现他了,动画有补间动画,属性动画,frame动画,那这里用那种呢,上面的分析中用到了平移,那么我们第一个想到的就是用属性动画来实现两个方向的平移,当然补间动画能实现的属性动画就能实现,我们先采用第一种方式。

两个球的xml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tag="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<Button
android:id="@+id/btn_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_gravity="center"
android:onClick="onClick"
android:text="贝兹曲线动画"
/>
<ImageView
android:id="@+id/ball"
android:layout_width="30dp"
android:layout_height="30dp"
android:background="@drawable/shape_img"
/>
<ImageView
android:id="@+id/ball2"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_gravity="right"
android:background="@drawable/shape_img"
/>
</RelativeLayout>

activity代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int[] startXy = new int[2];
int[] endXy = new int[2];
ball.getLocationInWindow(startXy);
ball2.getLocationInWindow(endXy);
TranslateAnimation tx = new TranslateAnimation(0, endXy[0] - startXy[0], 0, 0);
tx.setInterpolator(new LinearInterpolator());
TranslateAnimation ty = new TranslateAnimation(0, 0, 0, endXy[1] - startXy[1]);
ty.setInterpolator(new AccelerateInterpolator());
AnimationSet set = new AnimationSet(false);
set.addAnimation(tx);
set.addAnimation(ty);
set.setDuration(1000);
set.setRepeatCount(-1);
ball.startAnimation(set);

getLocationInWindow(int[] out):控件在其父窗口中的坐标位置
ball是左上角的红球,ball2是右下角的红球,这是第一种方式,比较简单,下面我们重点说下第二种方式
也就是我们标题中所说的用贝兹曲线来实现。关于贝兹曲线可以自行查阅资料

直接上代码吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public void beizi() {
final int[] startXy = new int[2];
final int[] endXy = new int[2];
ball.getLocationInWindow(startXy);
ball2.getLocationInWindow(endXy);
final ImageView imageView = new ImageView(PreferenceActivity.this);
ViewGroup.LayoutParams param = new LinearLayoutCompat.LayoutParams(60, 60);
imageView.setLayoutParams(param);
imageView.setBackgroundResource(R.drawable.shape_img);
ViewGroup rootView = (ViewGroup) PreferenceActivity.this.getWindow().getDecorView();
rootView.addView(imageView);
imageView.setX(startXy[0]);
imageView.setY(startXy[1]);
Point startPosition = new Point(startXy[0], startXy[1]);
Point endPostition = new Point(endXy[0], endXy[1]);
//控制点 中间点
int pointX = (startPosition.x + endPostition.x) / 2;
int pointY = startPosition.y;
//确定起点、终点 动态改变中间点
Point controllPoint = new Point(pointX, pointY);
BezierEvaluator bezierEvaluator = new BezierEvaluator(controllPoint);
ValueAnimator anim = ValueAnimator.ofObject(bezierEvaluator, startPosition, endPostition);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override public void onAnimationUpdate(ValueAnimator animation) {
Point point = (Point) animation.getAnimatedValue();
imageView.setX(point.x);
imageView.setY(point.y);
}
});
anim.setDuration(400);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
anim.addListener(new Animator.AnimatorListener() {
@Override public void onAnimationStart(Animator animation) {
}
@Override public void onAnimationEnd(Animator animation) {
ViewGroup parent = (ViewGroup) imageView.getParent();
if (parent != null) {
parent.removeView(parent);
}
}
@Override public void onAnimationCancel(Animator animation) {
}
@Override public void onAnimationRepeat(Animator animation) {
}
});
anim.start();
anim.setRepeatCount(-1);
}
public class BezierEvaluator implements TypeEvaluator<Point> {
private Point controllPoint;
public BezierEvaluator(Point controllPoint) {
this.controllPoint = controllPoint;
}
@Override public Point evaluate(float t, Point startValue, Point endValue) {
int x = (int) ((1 - t) * (1 - t) * startValue.x
+ 2 * t * (1 - t) * controllPoint.x
+ t * t * endValue.x);
int y = (int) ((1 - t) * (1 - t) * startValue.y
+ 2 * t * (1 - t) * controllPoint.y
+ t * t * endValue.y);
return new Point(x, y);
}
}

上面有几个关键步骤

  1. 确定动画的起点和终点坐标
  2. 确定控制点的坐标
  3. 用二阶贝兹曲线公式自定义TypeEvaluator以确定控制点的变化规律
  4. 在一次动画完成时,移除移动到终点的红球

本文主要是记录一次问题解决的方法。