有时候现有的控件无法满足我们的需求,打不到我们想要的效果,这时候就需要我们自己来写一个自定义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
);
}
}