引子
贝塞尔,全名-皮埃尔·贝塞尔,(1910年9月1日——1999年11月25日),法语:Pierre Bézier,法国机械和电气工程师,计算机几何建模创始人之一。
贝塞尔曲线,计算机图形学中相当重要的参数曲线--(吾等凡人的理解 ->_->简而言之就是,用路径上的几个点,做出一条光滑曲线)
之前写特效的时候,接触过 抛物线的计算公式,就是为了做出一个控件沿着弧形移动的动画。
其实做曲线的话,除了抛物线,还可以用这种 贝塞尔曲线,效果更好,使用更简单。
本人将粗略讲解原理(智商不够⊙︿⊙)以及详细展示用法;
强烈建议:
如果不知道神马是贝塞尔曲线的话,建议先看这个-
https://blog.csdn.net/yangdahuan/article/details/52174904,
里面有动态图展示,相当清楚明白; 看完了,再来看我写的具体用法,就会一目了然;
用法1
其实这种曲线算法的公式,是 完全脱离编程语言,以及完全脱离代码环境的,它纯粹就是一个数学概念,数学公式;
公式如下:
将此公式,转化为java代码,那就是:
1 /** 2 * N阶贝塞尔曲线 3 * 4 * @param t 5 * @param ps 曲线经过的点的X或者Y坐标 6 * 如果点数是2,则是1阶 7 * 如果点数是3,则是2阶 8 * ...... 9 * @return 10 */ 11 private double nOrderBezierFunction(double t, double... ps) { 12 double res = 0; 13 if (t > 1 || t < 0) return 0;// 如果参数t不是在0和1之间,也不用计算了 14 if (ps == null || ps.length < 2) return 0;// 只有1个点 或者一个点都没有,直接返回0 15 int N = ps.length - 1;// 确定阶数 16 Log.d("psTag", "" + N); 17 //现在转化公式; 18 for (int i = 0; i <= N; i++) { 19 double powRes; 20 powRes = ps[i] * Math.pow((1 - t), (N - i)) * Math.pow(t, i);//先把降幂和升幂公式转化出来 21 if (i != 0 && i != N) {//头和尾不需要诚意阶数,其他的都要乘阶数 22 powRes = N * powRes; 23 } 24 res += powRes; 25 } 26 return res; 27 }
上面的注释写的很清楚了;
然后调用的完整代码如下:
1 public class MainActivity extends AppCompatActivity { 2 3 @Override 4 protected void onCreate(Bundle savedInstanceState) { 5 super.onCreate(savedInstanceState); 6 setContentView(R.layout.activity_main); 7 final TextView tv_test = findViewById(R.id.tv_test); 8 tv_test.setOnClickListener(new View.OnClickListener() { 9 @Override 10 public void onClick(View v) { 11 anim(tv_test); 12 } 13 }); 14 } 15 16 double x = 0f; 17 double y = 0f; 18 19 private void anim(final View view) { 20 x = 0f; 21 y = 0f; 22 final RPoint p0 = new RPoint();//原点坐标就是view的所在 23 p0.mX = view.getX(); 24 p0.mY = view.getY(); 25 26 final RPoint p1 = new RPoint();//点p1 27 p1.mX = view.getX() + convertDIP2PX(30); 28 p1.mY = view.getY() + convertDIP2PX(250); 29 30 final RPoint p2 = new RPoint();//点p2 31 p2.mX = view.getX() + convertDIP2PX(60); 32 p2.mY = view.getY() - convertDIP2PX(250); 33 34 final RPoint p3 = new RPoint();//点p3 35 p3.mX = view.getX() + convertDIP2PX(90); 36 p3.mY = view.getY(); 37 38 ObjectAnimator goInAnim = new ObjectAnimator(); 39 goInAnim.setFloatValues(0f, 1f); 40 goInAnim.setDuration(5000); 41 goInAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 42 @Override 43 public void onAnimationUpdate(ValueAnimator valueAnimator) { 44 float t = (float) valueAnimator.getAnimatedValue(); 45 x = nOrderBezierFunction(t, p0.mX, p1.mX, p2.mX,p3.mX); 46 y = nOrderBezierFunction(t, p0.mY, p1.mY, p2.mY,p3.mY); 47 48 view.setX((float) x); 49 view.setY((float) y); 50 } 51 }); 52 goInAnim.setTarget(1); 53 goInAnim.start(); 54 } 55 56 private int convertDIP2PX(float dp) { 57 return (int) (dp * this.getResources().getDisplayMetrics().density + 0.5f); 58 } 59 60 class RPoint { 61 float mX; 62 float mY; 63 } 64 65 66 /** 67 * N阶贝塞尔曲线, 68 * 69 * @param t 70 * @param ps 曲线经过的点的X或者Y坐标,注意,这里的参数数量是不定的,你可以写2个,3个,或者100个。您随意。函数会自动计算贝塞尔坐标; 71 * 如果点数是2,则是1阶 72 * 如果点数是3,则是2阶 73 * ...... 74 * @return 75 */ 76 private double nOrderBezierFunction(double t, double... ps) { 77 double res = 0; 78 if (t > 1 || t < 0) return 0;// 如果参数t不是在0和1之间,也不用计算了 79 if (ps == null || ps.length < 2) return 0;// 只有1个点? 或者一个点都没有,直接返回0 80 int N = ps.length - 1;// 确定阶数 81 Log.d("psTag", "" + N); 82 //现在转化公式; 83 for (int i = 0; i <= N; i++) { 84 double powRes; 85 powRes = ps[i] * Math.pow((1 - t), (N - i)) * Math.pow(t, i);//先把降幂和升幂公式转化出来 86 if (i != 0 && i != N) {//头和尾不需要诚意阶数,其他的都要乘阶数 87 powRes = N * powRes; 88 } 89 res += powRes; 90 } 91 return res; 92 } 93 94 }
布局文件activity_main.xml:
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/tv_test" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </android.support.constraint.ConstraintLayout>
具体效果:
OK,以上就是直接使用公式的用法;代码可复用,喜欢的大佬们,欢迎拷贝。有错误请留言。
用法2
在android.graphics.Path类中,有直接绘制贝塞尔曲线的方法,
目前我发现的有
/**
绘制二阶贝塞尔曲线路径
* Add a quadratic bezier from the last point, approaching control point * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for * this contour, the first point is automatically set to (0,0). * * @param x1 The x-coordinate of the control point on a quadratic curve * @param y1 The y-coordinate of the control point on a quadratic curve * @param x2 The x-coordinate of the end point on a quadratic curve * @param y2 The y-coordinate of the end point on a quadratic curve */ public void quadTo(float x1, float y1, float x2, float y2) { isSimplePath = false; nQuadTo(mNativePath, x1, y1, x2, y2); }
* 添加一条二阶(或者二次)贝塞尔曲线,从上一个点,到达控制点(x1,y1),然后结束于 (x2,y2);
* 如果不在这个轮廓调用moveTo,第一个点将会默认为(0,0);调用这个quadTo之前,首先要先moveTo一个点,作为二阶贝塞尔曲线的起点;不然这个函数就会以原点(0,0)作为起点;
/** 绘制三阶贝塞尔曲线路径 * Add a cubic bezier from the last point, approaching control points * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been * made for this contour, the first point is automatically set to (0,0). * * @param x1 The x-coordinate of the 1st control point on a cubic curve * @param y1 The y-coordinate of the 1st control point on a cubic curve * @param x2 The x-coordinate of the 2nd control point on a cubic curve * @param y2 The y-coordinate of the 2nd control point on a cubic curve * @param x3 The x-coordinate of the end point on a cubic curve * @param y3 The y-coordinate of the end point on a cubic curve */ public void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) { isSimplePath = false; nCubicTo(mNativePath, x1, y1, x2, y2, x3, y3); }
* 添加一条立体的(三阶)贝塞尔曲线,从上一个点,依据 两个控制点(x1,y1) 和(x2,y2),以及终点(x3,y3)。
* 如果调用这个之前,没有调用moveTo,那么上一个点就会被设置成原点(0,0)
应该没有别的了,更高阶贝塞尔曲线的api,这里没有提供;
用法如下:
1 import android.content.Context; 2 import android.graphics.Canvas; 3 import android.graphics.Color; 4 import android.graphics.Paint; 5 import android.graphics.Path; 6 import android.graphics.Point; 7 import android.support.annotation.Nullable; 8 import android.util.AttributeSet; 9 import android.view.View; 10 11 public class BezierView extends View { 12 public BezierView(Context context) { 13 this(context, null); 14 } 15 16 public BezierView(Context context, @Nullable AttributeSet attrs) { 17 this(context, attrs, 0); 18 } 19 20 public BezierView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 21 super(context, attrs, defStyleAttr); 22 initPoints(); 23 initPaint(); 24 } 25 26 @Override 27 protected void onDraw(Canvas canvas) { 28 super.onDraw(canvas); 29 // drawBezier_2(canvas);//绘制二阶贝塞尔曲线 30 drawBezier_3(canvas);//绘制三阶贝塞尔曲线 31 } 32 33 Paint mPaint, mPaintForPoint, mPaintForControlPoint; 34 Path mPath; 35 float lineWidth = 5, pointWidth = 10; 36 37 Point p1, p2, p3;// 途经点 38 Point pControl_1, pControl_2;// 控制点,用于控制曲线的曲度 39 40 private void initPaint() { 41 mPaint = new Paint(); 42 mPath = new Path(); 43 mPaint.setStrokeWidth(lineWidth); 44 mPaint.setColor(Color.BLACK); 45 mPaint.setAntiAlias(true); 46 mPaint.setStyle(Paint.Style.STROKE); 47 48 mPaintForPoint = new Paint(); 49 mPaintForPoint.setStrokeWidth(pointWidth); 50 mPaintForPoint.setColor(Color.GREEN); 51 mPaintForPoint.setAntiAlias(true); 52 mPaintForPoint.setStyle(Paint.Style.FILL); 53 54 mPaintForControlPoint = new Paint(); 55 mPaintForControlPoint.setStrokeWidth(pointWidth); 56 mPaintForControlPoint.setColor(Color.BLUE); 57 mPaintForControlPoint.setAntiAlias(true); 58 mPaintForControlPoint.setStyle(Paint.Style.FILL); 59 } 60 61 private void initPoints() { 62 p1 = new Point(20, 20); 63 p2 = new Point(300, 300); 64 p3 = new Point(500, 20); 65 pControl_1 = new Point(20, 300); 66 pControl_2 = new Point(300, 20); 67 } 68 69 /** 70 * 绘制二阶贝塞尔曲线,顺便把途径的点,和控制点都画出来 71 */ 72 private void drawBezier_2(Canvas canvas) { 73 //途径点以及控制点 74 canvas.drawPoint(p1.x, p1.y, mPaintForPoint); 75 canvas.drawPoint(p2.x, p2.y, mPaintForPoint); 76 canvas.drawPoint(pControl_1.x, pControl_1.y, mPaintForControlPoint); 77 78 // 重置路径 79 mPath.reset(); 80 mPath.moveTo(p1.x, p1.y);// 绘制贝塞尔曲线之前,要指定一个起点,如果你不指定,他就会以原点为起点 81 mPath.quadTo(pControl_1.x, pControl_1.y, p2.x, p2.y);//绘制二阶贝塞尔曲线,需要提供一个控制点的坐标,一个终点坐标,(还有一个起点坐标,只不过是在上一步设置的) 82 83 canvas.drawPath(mPath, mPaint); 84 } 85 86 /** 87 * 绘制三阶贝塞尔曲线,顺便把途径的点,和控制点都画出来 88 */ 89 private void drawBezier_3(Canvas canvas) { 90 canvas.drawPoint(p1.x, p1.y, mPaintForPoint); 91 canvas.drawPoint(p3.x, p3.y, mPaintForPoint); 92 93 canvas.drawPoint(pControl_1.x, pControl_1.y, mPaintForControlPoint); 94 canvas.drawPoint(pControl_2.x, pControl_2.y, mPaintForControlPoint); 95 // 重置路径 96 mPath.reset(); 97 mPath.moveTo(p1.x, p1.y);// 绘制贝塞尔曲线之前,要指定一个起点,如果你不指定,他就会以原点为起点 98 mPath.cubicTo(pControl_1.x, pControl_1.y, pControl_2.x, pControl_2.y, p3.x, p3.y); 99 100 canvas.drawPath(mPath, mPaint); 101 } 102 103 }
绘制的三阶贝塞尔曲线如图:
绘制的二阶贝塞尔曲线如下:
蓝色的点,是控制点,可以看到,曲线在绘制的过程中在尽量靠近控制点;
绿色的点是 途经点(在这里也可以理解为 起点 和终点,因为 这两个api只需要起点和终点,其他的都是控制点)
======================================================================================
结语
利用贝塞尔曲线做特效,上面的两种方式各有千秋,但是基本上脱离不了 数学公式原理,只不过 后者将公式写进了API,前者我们可以自由发挥。
写得很基础,毕竟万丈高楼平地起。
新技能get··以后要做曲线不用愁了,比如,移动的波浪,双重移动波浪等·······
我已经看到吊炸天的特效在向我招手了,(●´∀`●)·~~~~~~~~~~~~~