贝塞尔曲线原理及应用

今天在学习贝塞尔曲线的过程中觉得很新奇,特别是之前觉得很神秘的东西一下全部融会贯通了,为了实践,特地写了一个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——贝塞尔曲线实现水波纹效果

发布了61 篇原创文章 · 获赞 28 · 访问量 10万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章