贝塞尔曲线原理及应用

来源:互联网 发布:linux数据库启动命令 编辑:程序博客网 时间:2024/06/17 23:38
今天在学习贝塞尔曲线的过程中觉得很新奇,特别是之前觉得很神秘的东西一下全部融会贯通了,为了实践,特地写了一个demo——波浪图,先看效果图:

这里写图片描述

纸上得来终觉浅,绝知此事要躬行!本来觉得挺简单的一件事结果各种坑!什么,你说贝塞尔曲线不简单?no,no,看看大神们是怎么总结的?

二阶贝塞尔曲线形成原理:

1.连接 A,B 形成 AB 线段,连接 B,C 形成 BC 线段。

连成AB,BC线段

2.在 AB 线段取一个点 D,BC 线段取一个点 E ,使其满足条件: AD/AB = BE/BC,连接 D,E 形成线段 DE。

连接DE

3.在 DE 取一个点 F,使其满足条件:AD/AB = BE/BC = DF/DE。

这里写图片描述

4.而满足这些条件的所有的F点所形成的轨迹就是二阶贝塞尔曲线,动态过程如下:

这里写图片描述

上面的代码解释得很清楚了,已知D,可以求得E,已知D、E可以求得F,如果D为一个变量,则F在二维平面就是一条曲线,如图四。

那么知道了二阶贝塞尔曲线的原理以后,怎么运用呢?

方法预览:

public void quadTo (float x1, float y1, float x2, float y2)

怎么用:

因为二阶贝塞尔曲线需要三个点才能确定,所以quadTo方法中的四个参数分别是确定第二,第三的点的。第一个点就是path上次操作的点。
现在用一个实例来练习下这个方法:

水波纹效果:

这里写图片描述

实现思路:

这里写图片描述

我看见这幅图的第一思路是首先得到波浪长度mWL、波浪中心点mCenterY。因为图中的波浪宽度为整个屏幕宽度,所以我们需要得到屏幕的宽度。百度一下方法就出来了:

方法一:

       //获取屏幕的宽度      public static int getScreenWidth(Context context) {          WindowManager manager = (WindowManager) context                  .getSystemService(Context.WINDOW_SERVICE);          Display display = manager.getDefaultDisplay();          return display.getWidth();      }      //获取屏幕的高度      public static int getScreenHeight(Context context) {          WindowManager manager = (WindowManager) context                  .getSystemService(Context.WINDOW_SERVICE);          Display display = manager.getDefaultDisplay();          return display.getHeight();      }  

方法二:

DisplayMetrics  dm = new DisplayMetrics();      getWindowManager().getDefaultDisplay().getMetrics(dm);      int screenWidth = dm.widthPixels;                int screenHeight = dm.heightPixels;    

由于第二种方法只能在Activity里面使用,所以这里使用第一种方法。但是在写的过程中我又发现一个提醒:

这里写图片描述

有强迫症的我马上去官网查了一下,发现官网推荐的是另一个方法,于是马上就改用了另一个方法:

这里写图片描述

这里写图片描述

于是,我们现在就可以根据波浪的宽度和中心点绘制一条静态的波浪线,代码如下:

 @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        mPath.reset();        mPath.moveTo(-mWL, mCenterY); //将path操作的起点移动到(-mWL,mCenterY)       mPath.quadTo((-mWL * 3 / 4) , mCenterY + 60, (-mWL / 2), mCenterY); //画出第一段波纹的第一条曲线       mPath.quadTo((-mWL / 4) , mCenterY - 60, 0, mCenterY); //画出第一段波纹的第二条曲线       mPath.quadTo((mWL /4) , mCenterY + 60, (mWL / 2), mCenterY); //画出第二段波纹的第一条曲线       mPath.quadTo((mWL * 3/ 4) , mCenterY - 60, mWL, mCenterY);  //画出第二段波纹的第二条曲线        mPath.lineTo(mScreenWidth, mScreenHeight);        mPath.lineTo(0, mScreenHeight);        mPath.close();        canvas.drawPath(mPath,mPaint);    }

查看加载效果的时候发现一个坑,就是荣耀的真机显示绘制结果的时候左边居然有一条很宽的线,这个时候为了检查不是贝塞尔曲线的问题,我还在贝塞尔曲线X方向起点到终点绘制了一条直线,但是依然如此。

这里写图片描述

然后我用自己的小米5测试了一下,居然完全没有任何问题:

这里写图片描述

虽然后来发现是因为使用了ConstraintLayout布局,然后自定义控件左边距有8dp,然后给自定义控件写死了宽度,因此小米5的宽度刚刚合适,,,,,

修改了以后正常了,于是继续将静态的波浪线调整成动态的波浪线。其实逻辑是很简单,就是给自定义View一个点击事件,然后在时间中启动一个属性动画,动态的修改波浪线水平方向右移的位置,全部代码写出来:

/** * 自定义波浪图 * Created by 魏兴 on 2017/6/12. */public class WaveView1 extends View implements View.OnClickListener{    private static final String TAG = "WaveView";    private Context mContext;    /**     * 波纹段长度     */    private int mWL;    /**     * 波浪高度中点     */    private float mCenterY;    private float mScreenWidth;    private float mScreenHeight;    private int mOffset;    private int mWaveCount;    private Path mPath;    private Paint mPaint;    private Paint mCyclePaint;    public WaveView1(Context context) {        super(context);        this.mContext = context;//        init();    }    public WaveView1(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        this.mContext = context;//        init();    }    public WaveView1(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        this.mContext = context;//        init();    }    private void init() {        WindowManager manager = (WindowManager) mContext .getSystemService(Context.WINDOW_SERVICE);        Display display = manager.getDefaultDisplay();        Point po = new Point();        display.getSize(po);        mWL = po.x;        mCyclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);        mCyclePaint.setColor(Color.RED);        mCyclePaint.setStyle(Paint.Style.FILL_AND_STROKE);    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        mPaint = new Paint();       // 创建画笔        mPaint.setColor(Color.BLUE);  // 画笔颜色 - 黑色        mPaint.setAntiAlias(true);        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);  // 填充模式 - 填充        mPath = new Path();        mWL = w*3/4;        mCenterY = h/2f;        mScreenHeight = h;        mScreenWidth = w;        //加1.5:至少保证波纹有2个,至少2个才能实现平移效果        mWaveCount = (int) Math.round(mScreenWidth / mWL + 1.5);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        mPath.reset();        //移到屏幕外最左边        mPath.moveTo(-mWL + mOffset, mCenterY);        for (int i = 0; i < mWaveCount; i++) {            //正弦曲线            mPath.quadTo((-mWL * 3 / 4) + (i * mWL) + mOffset, mCenterY + 60, (-mWL / 2) + (i * mWL) + mOffset, mCenterY);            mPath.quadTo((-mWL / 4) + (i * mWL) + mOffset, mCenterY - 60, i * mWL + mOffset, mCenterY);            //测试红点//            canvas.drawCircle((-mWL * 3 / 4) + (i * mWL) + mOffset, mCenterY + 60, 5, mCyclePaint);//            canvas.drawCircle((-mWL / 2) + (i * mWL) + mOffset, mCenterY, 5, mCyclePaint);//            canvas.drawCircle((-mWL / 4) + (i * mWL) + mOffset, mCenterY - 60, 5, mCyclePaint);//            canvas.drawCircle(i * mWL + mOffset, mCenterY, 5, mCyclePaint);        }        //填充矩形        mPath.lineTo(mScreenWidth, mScreenHeight);        mPath.lineTo(0, mScreenHeight);        mPath.close();        canvas.drawPath(mPath, mPaint);    }    @Override    public void onClick(View view) {        try {            ValueAnimator animator = ValueAnimator.ofInt(-mWL, mWL);            animator.setDuration(1000);            animator.setRepeatCount(ValueAnimator.INFINITE);            animator.setInterpolator(new LinearInterpolator());            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {                @Override                public void onAnimationUpdate(ValueAnimator animation) {                    mOffset = (int) animation.getAnimatedValue();                    postInvalidate();                }            });            animator.start();        } catch (Exception e) {            e.printStackTrace();        }    }}

然后在实例化控件以后调用Click方法(不能直接点击View,因为Click方法此处并没有响应,需要重写boolean onTouchEvent(MotionEvent event)方法来定义Click事件——我猜的哈,反正我直接点击的时候此处方法并没有响应)。于是我简单申明控件以后直接调用了Click方法。大坑出现了!

这里写图片描述

此处波浪图并没有动起来,而且属性方法里面打印的日志显示设置的ofint()方法参数(这个是成员变量,即波浪宽度)为900,偏移量为0。上网百度“属性动画失效”找不到资料,后来查了半天发现是因为Activity里面声明控件并调用了Click方法,这个时候宽度默认为0,因此属性动画的偏移量一直为0。
于是修改调用代码:

public class WaveActivity extends AppCompatActivity {    private WaveView1 mWave;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_wave);        mWave = (WaveView1) findViewById(R.id.waveView);        final Handler handler = new Handler();        handler.postDelayed(new Runnable() {            @Override            public void run() {                handler.post(new Runnable() {                    @Override                    public void run() {                        mWave.onClick(null);                    }                });            }        },500);    }}

续三阶贝塞尔曲线形成原理:

1.连接 A,B 形成 AB 线段,连接 B,C 形成 BC 线段,连接 C,D 形成 CD 线段。

这里写图片描述

2.在AB线段取一个点 E,BC 线段取一个点 F,CD 线段取一个点 G,使其满足条件: AE/AB = BF/BE = CG/CD。连接 E,F 形成线段 EF,连接 F,G 形成线段 FG。

这里写图片描述

3.在EF线段取一个点 H,FG 线段取一个点 I,使其满足条件: AE/AB = BF/BE = CG/CD = EH/EF = FI/FG。连接 H,I 形成线段 HI。

这里写图片描述

4.在 HI 线段取一个点 J,使其满足条件: AE/AB = BF/BE = CG/CD = EH/EF = FI/FG = HJ/HI。

这里写图片描述

5.而满足这些条件的所有的J点所形成的轨迹就是三阶贝塞尔曲线,动态过程如下:

这里写图片描述

方法预览:
public void quadTo (float x1, float y1, float x2, float y2)
绘制圆形:
这里写图片描述
修改坐标:
这里写图片描述
最终效果:
这里写图片描述

最后再来一张效果图:

这里写图片描述

参考资料:

Path从懵逼到精通(2)——贝塞尔曲线

Android自定义View——贝塞尔曲线实现水波纹效果

原创粉丝点击