一、贝塞尔曲线
1. 定义
贝塞尔曲线,是应用于二维图形应用程序的数学曲线,由线段和节点组成,节点是可拖动的支点,线段像可伸缩的皮筋。
2. 公式与效果
(1)线性贝塞尔曲线
(2)二阶贝塞尔曲线
(3)三阶贝塞尔曲线
(4)n阶贝塞尔曲线
二、绘制曲线
自定义View,绘制曲线,代码如下:
//自定义View
public class CurveView extends View {
private Path mPath;
private Paint mPaint;
public CurveView(Context context) {
super(context);
init();
}
public CurveView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public CurveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPath = new Path();
mPath.moveTo(300, 300);
mPath.lineTo(500, 300);
mPath.quadTo(700, 500, 500, 700); //二阶贝塞尔曲线
mPath.lineTo(300, 700);
mPath.quadTo(100, 500, 300, 300);
mPaint = new Paint();
mPaint.setColor(Color.BLUE);
mPaint.setStyle(Paint.Style.STROKE); //设置为空心
mPaint.setStrokeWidth(5);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(mPath, mPaint);
}
}
//布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.tomorrow.androidtest6.CurveView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
//代码,MAinActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
三、自定义曲线路径动画
//贝塞尔曲线类
public class BesselCurve {
public static float oneBesselCurve(float t, float start, float end) {
float result = (1 - t) * start + t * end;
return result;
}
public static float twoBesselCurve(float t, float start, float control1, float end) {
float result = (1 - t) * (1 - t) * start + 2 * t * (1 - t) * control1 + t * t * end;
return result;
}
public static float threeBesselCurve(float t, float start, float control1, float control2, float end) {
float result = start * (1 - t) * (1 - t) * (1 - t) + 3 * control1 * t * (1 - t) * (1 - t) + 3 * control2 * t * t *(1 - t) + end * t * t * t;
return result;
}
}
//路径点坐标
public class PathPoint {
public static final int PATH_MOVE = 0;
public static final int PATH_ONE_BESSEL = 1;
public static final int PATH_TWO_BESSEL = 2;
public static final int PATH_THREE_BESSEL = 3;
private int operation;
private float endX, endY;
private float control1X, control1Y, control2X, control2Y;
//move操作跟一阶贝塞尔操作都通过该构造方法创建终点坐标
public PathPoint(int operation, float endX, float endY) {
this.operation = operation;
this.endX = endX;
this.endY = endY;
}
//二阶贝塞尔操作通过该构造方法创建控制点跟终点坐标
public PathPoint(int operation, float control1X, float control1Y, float endX, float endY) {
this.operation = operation;
this.control1X = control1X;
this.control1Y = control1Y;
this.endX = endX;
this.endY = endY;
}
//三阶贝塞尔操作通过该构造方法创建控制点跟终点坐标
public PathPoint(int operation, float control1X, float control1Y, float control2X, float control2Y, float endX, float endY) {
this.operation = operation;
this.control1X = control1X;
this.control1Y = control1Y;
this.control2X = control2X;
this.control2Y = control2Y;
this.endX = endX;
this.endY = endY;
}
public void setOperation(int operation) {
this.operation = operation;
}
public int getOperation() {
return operation;
}
public void setEndX(float endX) {
this.endX = endX;
}
public float getEndX() {
return endX;
}
public void setEndY(float endY) {
this.endX = endX;
}
public float getEndY() {
return endY;
}
public void setControl1X(float control1X) {
this.control1X = control1X;
}
public float getControl1X() {
return control1X;
}
public void setControl1Y(float control1Y) {
this.control1Y = control1Y;
}
public float getControl1Y() {
return control1Y;
}
public void setControl2X(float control2X) {
this.control2X = control2X;
}
public float getControl2X() {
return control2X;
}
public void setControl2Y(float control2Y) {
this.control2Y = control2Y;
}
public float getControl2Y() {
return control2Y;
}
}
//路径集合
public class PathSet {
private List<PathPoint> mPathSet = new ArrayList<>();
public void moveTo(float endX, float endY) {
PathPoint pathPoint = new PathPoint(PathPoint.PATH_MOVE, endX, endY);
mPathSet.add(pathPoint);
}
public void oneBesselCurveTo(float endX, float endY) {
PathPoint pathPoint = new PathPoint(PathPoint.PATH_ONE_BESSEL, endX, endY);
mPathSet.add(pathPoint);
}
public void twoBesselCurveTo(float control1X, float control1Y, float endX, float endY) {
PathPoint pathPoint = new PathPoint(PathPoint.PATH_TWO_BESSEL, control1X, control1Y, endX, endY);
mPathSet.add(pathPoint);
}
public void threeBesselCurveTo(float control1X, float control1Y, float control2X, float control2Y, float endX, float endY) {
PathPoint pathPoint = new PathPoint(PathPoint.PATH_THREE_BESSEL, control1X, control1Y, control2X, control2Y, endX, endY);
mPathSet.add(pathPoint);
}
public List<PathPoint> getPathSet() {
return mPathSet;
}
public void clear() {
mPathSet.clear();
}
}
//利用贝塞尔曲线类自定义估值器
public class PathEvaluator implements TypeEvaluator<PathPoint> {
/**
* @param fraction 动画执行的百分比
* @param startValue 起点
* @param endValue 终点
* @return PathPoint 根据动画执行的百分比计算出当前点的坐标
*/
@Override
public PathPoint evaluate(float fraction, PathPoint startValue, PathPoint endValue) {
float currentX = 0, currentY = 0;
switch(endValue.getOperation()) {
case PathPoint.PATH_MOVE:
currentX = endValue.getEndX();
currentY = endValue.getEndY();
break;
case PathPoint.PATH_ONE_BESSEL:
currentX = BesselCurve.oneBesselCurve(fraction, startValue.getEndX(), endValue.getEndX());
currentY = BesselCurve.oneBesselCurve(fraction, startValue.getEndY(), endValue.getEndY());
break;
case PathPoint.PATH_TWO_BESSEL:
currentX = BesselCurve.twoBesselCurve(fraction, startValue.getEndX(), endValue.getControl1X(), endValue.getEndX());
currentY = BesselCurve.twoBesselCurve(fraction, startValue.getEndY(), endValue.getControl1Y(), endValue.getEndY());
break;
case PathPoint.PATH_THREE_BESSEL:
currentX = BesselCurve.threeBesselCurve(fraction, startValue.getEndX(), endValue.getControl1X(), endValue.getControl2X(), endValue.getEndX());
currentY = BesselCurve.threeBesselCurve(fraction, startValue.getEndY(), endValue.getControl1Y(), endValue.getControl2Y(), endValue.getEndY());
break;
default:
break;
}
return new PathPoint(PathPoint.PATH_MOVE, currentX, currentY);
}
}
//布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/main_tv"
android:layout_width="50px"
android:layout_height="50px"
android:background="@color/colorAccent"/>
</RelativeLayout>
//代码,MAinActivity
private void startBesselCurveAnimator() {
PathSet pathSet = getPathSet();
//"this"表示属性动画的作用对象为当前Activity对象
//"pathPoint"为自定义属性名,在当前Activity对象中必须要有public访问权限的"setPathPoint()"方法设置"pathPoint"属性值
ObjectAnimator objectAnimator = ObjectAnimator.ofObject(this, "pathPoint", new PathEvaluator(), pathSet.getPathSet().toArray());
objectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
objectAnimator.setRepeatCount(2);
objectAnimator.setRepeatMode(ObjectAnimator.REVERSE);
objectAnimator.setDuration(5000);
objectAnimator.start();
}
private PathSet getPathSet() {
PathSet pathSet = new PathSet();
pathSet.moveTo(300,300);
pathSet.oneBesselCurveTo(500, 300);
pathSet.twoBesselCurveTo(700, 500, 500, 700);
pathSet.oneBesselCurveTo(300, 700);
pathSet.twoBesselCurveTo(100, 500, 300, 300);
return pathSet;
}
public void setPathPoint(PathPoint pathPoint) { //自定义属性"pathPoint"的设置方法,具有public访问权限
mMain_tv.setTranslationX(pathPoint.getEndX()); //设置TextView的x轴方向的偏移量
mMain_tv.setTranslationY(pathPoint.getEndY()); //设置TextView的y轴方向的偏移量
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
startBesselCurveAnimator();
}