贝塞尔曲线原理及应用

时间:2022-10-02 05:46:12
今天在学习贝塞尔曲线的过程中觉得很新奇,特别是之前觉得很神秘的东西一下全部融会贯通了,为了实践,特地写了一个demo——波浪图,先看效果图:

贝塞尔曲线原理及应用

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

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

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

贝塞尔曲线原理及应用

2.在 AB 线段取一个点 D,BC 线段取一个点 E ,使其满足条件: AD/AB = BE/BC,连接 D,E 形成线段 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——贝塞尔曲线实现水波纹效果