有时候现有的控件无法满足我们的需求,打不到我们想要的效果,这时候就需要我们自己来写一个自定义view,来满足自己的需求。
而贝塞尔曲线,就是其中的一种方式,比如说我们需要画一个弧形的线条或者是图形,这时候就可以使用贝塞尔曲线来进行,最简单的就是画一个圆形。
下面就是介绍一下贝塞尔曲线:
贝塞尔曲线,就是通过一个起始点,一个结束点,中间对应不同的数量的条件点组成,每条线的距离可以不同,长度也可以不同,所连接的角度也不同。
最简单的,就是只有起始点和结束点的,就是一条直线。。。
之后,就是三条线的贝塞尔曲线,根据角度和长度不同可以形成半圆或者是扭曲的圆。
以两条线三点的贝塞尔曲线为例,第一个是起始点,第二个是结束点,中间还有一个条件点,三条线链接形成一个夹角,三个角分为ABC。
然后,我们可以设定为由A到B的时间为0-1,B到C的时间也是0-1,当开始画图的时候,从A到B的点与B到C的点相连成一条线,在这条线上,有个点D以同样的时间进度前进,从A开始,随着进度到C,在这期间所画成的弧线就是贝塞尔曲线。
以图为例,每一条相同颜色 的线,就是连接的线,上面的那一圈黑点就是随着移动在线上的移动位置所经过的位置,当这些点的距离够近并且相连起来的时候,就是一个曲线,每一条的移动距离是不一定的,但是从头到尾的移动时间都是一样的,也就是在0-1之间走完。
比如,那个处于中间位置的蓝色,当A到B的线上走到0.5的位置,那么B到C的距离也一定是0.5,而在那条蓝色的线上的点,处于的位置也一定是0.5,随着增加而增加。
也就是说,所有的点的进度,都是相同的,无论所在的线的长短。
这就是要给二阶的贝塞尔曲线,这样就算是其他更高阶的贝塞尔曲线也是同样的方式处理。
三阶的贝塞尔曲线是三条线,四阶的是四条线:
而运算的方式都是一样的,将两个相近的线相连,三阶的就会成为两条线,在将两条线相连,就会形成第三条线,而第三条线的移动和上面的点经过的位置就是曲线的移动路线,四阶的一样。
这是贝塞尔曲线的运算公式,每次随着增加,运算的平方都会跟着增加,理论上可以无上限,但是我们平时并不会用到那么多就是了,基本上三阶的就够了。
下面就是通过自定义View来实现1~3阶的贝塞尔曲线,因为在系统里直接就有1~3阶的计算公式,我们只要直接调用本身的就可以了。
public class
Text_3_2_1 extends
View {
public
Text_3_2_1(Context context) {
super(context);
init();
}
public
Text_3_2_1(Context context, @Nullable
AttributeSet attrs) {
super(context, attrs);
init();
}
public
Text_3_2_1(Context context, @Nullable
AttributeSet attrs, int
defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
//画笔
private final
Paint mPaint=new
Paint(Paint.ANTI_ALIAS_FLAG);
private final
Path mPath=new
Path();
//初始化方法
private void
init(){
Paint paint=mPaint;
//画笔设置
//抗锯齿
paint.setAntiAlias(true);
//抗抖动
paint.setDither(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(5);
//一阶的贝塞尔曲线只有两个点
//起始点
Path path=mPath;
path.moveTo(50,50);
//结束点
path.lineTo(50,800);
/**
*
二阶贝塞尔曲线,是从上一个的结束,400开始的
*
后面的数是结束点,前面的xy是中轴线,是上一个的结尾和后面的中间位置
*
这种方式适合自己给定固定好的点
*/
// path.quadTo(300,100,400,200);
/**
*
相对的实现二阶贝塞尔曲线,相对于上一次结束的点
*
相对于上一个结束点,先是控制点,对应着结束自己增加与减少,第二个结束点也一样。
*
也就是,用结束点加上数字得到控制点的数字300+100.。。
*
这个不用考虑固定的坐标,只要考虑之后的增减就可以,会随着上一个的移动而自己调节
*
适合不固定的点
*/
// path.rQuadTo(100,-100,200,0);
// path.moveTo(200,400);
/**
*
三阶贝塞尔曲线,有两个控制点,这个是给出固定坐标点的
*第一对是第一个控制点的x,y
*
第二对的控制点的X,Y,
*
第三个则是最后的结束点
*
画出一个曲线
*/
// path.cubicTo(300,100,400,400,500,200);
/**
*
相对的实现三阶贝塞尔曲线
*
与二阶的样,进行运算加减
* 50,800)
*/
path.rCubicTo(100,-400,300,-200,500,-400);
}
@Override
protected void
onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘画
canvas.drawPath(mPath,
mPaint);
//打印控制点
canvas.drawPoint(300,100,mPaint);
canvas.drawPoint(400,400,mPaint);
}
}
而从四阶以上,就需要我们自己进行计算,因为里面就已经没有公式了,而且因为需要循环计算从0-1之间的移动,消耗的资源也是很多的,下面就是代码。
public class
BezierView extends
View{
public
BezierView(Context context) {
super(context);
init();
}
public
BezierView(Context context, @Nullable
AttributeSet attrs) {
super(context, attrs);
init();
}
public
BezierView(Context context, @Nullable
AttributeSet attrs, int
defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private
Path mBezier=new
Path();
private
Paint mPaint=new
Paint(Paint.ANTI_ALIAS_FLAG);
public void
init(){
Paint paint=mPaint;
//画笔设置
//抗锯齿
paint.setAntiAlias(true);
//抗抖动
paint.setDither(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10);
//初始化贝塞尔曲线4阶~~往上
initBezier();
}
private void
initBezier(){
//(0,0),(300,300),(200,700),(500,500),(700,1200)
float[] xPoints=new
float[]{0,100,200,300,400};
float[] yPoints=new
float[]{300,600,100,500,300};
// float pregress=0.2f;//...
Path path=mBezier;
int
fps=100;
for
(int
i=0;i<=100;i++){
//进度
float
pregress=i/(float)fps;
float
x = calculateBezier(pregress, xPoints);
float
y = calculateBezier(pregress, yPoints);
//使用链接的方式,当xy变动足够小的情况下,就是平滑曲线了
Log.e("x...",".."+x);
Log.e("y...",".."+y);
path.lineTo(x,y);
}
}
/**
*
*
计算某时刻的贝塞尔所处的值(x或Y)
*
@param
t
时间(0~1)
*
@param
values
贝塞尔点集合(x或y)
*
@return
当前t时刻的贝塞尔所处点
*/
private float
calculateBezier(float
t,float... values){
//才用双层循环
//首先得到当前的长度
final int
len=values.length;//初始=4
//外层就是当前长度-1的循环
//i初始=3,核心部分
for
(int
i=len-1;i>0;i--){
//外层
for
(int
j=0;j<i;j++){
//内层,进行计算
/**
*
两点之间进行计算的公式
*
第一个点+后一个点减去第一个点的差值乘以时间t
*
算出来的再加上第一个点的就是第一个点到第二个点的距离
*/
values[j]=values[j]+(values[j+1]-values[j])*t;
}
}
//运算时结构保存在第一位
//所以,我们返回第一位
return
values[0];
}
@Override
protected void
onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(mBezier,mPaint);
}
}