zoukankan      html  css  js  c++  java
  • android-贝塞尔(bezier)曲线

    引子

    贝塞尔,全名-皮埃尔·贝塞尔,(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··以后要做曲线不用愁了,比如,移动的波浪,双重移动波浪等·······

    我已经看到吊炸天的特效在向我招手了,(●´∀`●)·~~~~~~~~~~~~~

  • 相关阅读:
    python学习===从一个数中分解出每个数字
    python学习===复制list
    Jmeter===测试案例参考
    Jmeter==HTTP信息头管理器的作用
    python实战===使用随机的163账号发送邮件
    python实战===实现读取txt每一行的操作,账号密码
    python实战===生成随机数
    python实战===输入密码以******的形式在cmd中展示
    python实战===使用smtp发送邮件的源代码,解决554错误码的问题,更新版!
    python实战===使用smtp发送邮件的源代码,解决554错误码的问题
  • 原文地址:https://www.cnblogs.com/hankzhouAndroid/p/9524924.html
Copyright © 2011-2022 走看看