说到贝塞尔曲线各位前端的小伙伴一定不陌生,贝塞尔曲线是一段优美的曲线,他可以
极大程度的提高我们程序的美观性,试想我们的应用如果只有简单的矩形、圆形、三角形巴
拉巴拉的基础图形,很难达到我们期望的良好的交互效果。我们如何去随性所欲的裁切、遮
盖、绘制我们想要的界面的,今天的主角——贝塞尔曲线,必不可少。
开门见山,要学会贝塞尔曲线,肯定必须要了解,什么是贝塞尔曲线啊。不知道有没有小伙伴和我一样,一开始有被这个名字吓到,第一感觉就是:高端,复杂,难搞。所以别慌,接下来它不叫贝塞尔了,我就叫他:简单曲线!
OK,不搞大家心态了,其实没有难学的技术,只怕肯钻研的开发。给大家揭开它的神秘面纱了:
贝塞尔曲线(Bezier curve)是计算机图形学中相当重要的参数曲线,它通过一个方程来描述一条曲线,根据方程的最高阶数,又分为线性贝赛尔曲线,二次贝塞尔曲线、三次贝塞尔曲线和更高阶的贝塞尔曲线。
如上所述,贝塞尔曲线有很多次,二次、三次、四次…,今天主要给各位介绍的是二次的贝塞尔曲线。
二次贝塞尔曲线由三个点P0,P1,P2来确定,这些点也被称作控制点。曲线的方程为:
啊?我人裂开了,这人都看傻了呀,写的啥呀?别急,一点一点的来剥离这个公式。首先根据第一句话,我们可以明确的是,公式中的P0,P1,P2分别代表的是三个点。这个t呢?这个t各位可以暂时看作一个从0到1之间的一个数。这样整个公式中的各个字符所代表的含义也就明确了。
看图细说(必看):
选定一个0~1的t值
通过P0和P1计算出点Q0,Q0在P0 P1连成的直线上,并且length( P0, Q0 ) = length( P0, P1 ) * t
同样,通过P1和P2计算出Q1,使得length( P1, Q1 ) = length( P1, P2 ) * t
再重复一次这个步骤,通过Q1和Q2计算出B,使得length( Q0, Q1 ) = length( Q0, B ) * t。B就为当前曲线上的点
注:上面的length表示两点之间的长度。
哎,认真阅读完上面一段,并且理解的同学,应该已经发现,我们通过上面的手段,得到了一个点——点B。那么,如何由点到线的过渡呢?追本溯源,各位看一下我们最开始的公式,我们一开始就假设了t是一个常量,但其实不然。这个t就是我们画出这条曲线的方法。如果将t的值从0过渡到1,不断计算点B,就可以得到一条二次贝塞尔曲线:
其实在canvas中已经有绘制二次贝塞尔曲线,在Flutter中就是这个方法:
/// Adds a quadratic bezier segment that curves from the current
/// point to the given point (x2,y2), using the control point
/// (x1,y1).
void quadraticBezierTo(double x1, double y1, double x2, double y2) native 'Path_quadraticBezierTo';
这个方法中我们传入了看起来是四个坐标?好像很复杂的样子?一点都不复杂,咱们在上文中已经解开了疑惑。其中x1, y1, x2, y2即为后两个控制点(P1和P2)的横纵坐标。那P0勒?P0就是你当前所在的位置。说的还是很抽象,来实战吧~
起飞 ️
打开DartPad,新建一个Flutter Pad,我们的第一步就要新建一个Container,放入我们的主题文字,给上一个背景色,没啥技术含量,我一步带过了~,希望各位跟上。
然后我们要在我们的Container外层嵌套一个裁切组件,用于裁切我们这个组件。我们用的组件就是ClipPath,顾名思义,我们这个组件既然是裁切所用,各位想想,一个裁衣工,在裁剪衣服时依据是什么?是裁衣服的路线呀~,所以我们这个组件中的必要参数:clipper就是一个自定义的路线罢了。
我们嵌套完成以后,来定义我们的裁切路线咯,首先看到的是两个必须实现的父类方法,一个返回Path的方法不用说,肯定是我们的路线,另一个方法shouldReclip是让我们决定是否需要重新裁切✂️,这不是我们的重点,直接给个true完事儿。
万事俱备,正式开工咯!我们来新建一个path,我们的起点是坐标原点,也就是左上角,我们认为是(0,0),然后我们首先到了哪个点呢?
path.lineTo(0, size.height - 80);
这是哪个点?是我们从原点直接向下走,走到离我们的Container高度还剩80处,我们要开始画贝塞尔曲线了!上面就说过,我们要画贝塞尔曲线,我们要传入两个点的坐标!是后面两个点!P1和P2,自己定义两个呗。定义好以后我们调用path自带绘制贝塞尔方法试试。
/// P1
var controllPoint = Offset(50, size.height);
/// P2
var endPoint = Offset(size.width/2, size.height);
/// 绘制贝塞尔曲线
path.quadraticBezierTo(controllPoint.dx, controllPoint.dy, endPoint.dx, endPoint.dy);
有人会问,P0跑哪儿去了,其实不难发现,P0就是我们最开始走到的那个距离Container下边界还有80的点,也就是在贝塞尔中的起点~,来看一下效果图吧~我已经帮各位把点都标注出来了。
看见这美丽的弧度了吗?虽然很普通,但确实是我们的贝塞尔的起源啊,有一个弧度就有一万种弧度!但是好像…我们切的有点多,为啥捏,因为我们的终点就到了P2,起点是左上角,这连起来确实是这样~我们只需要把我们未走完的路走完就行了~
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Column(children: [
ClipPath(
clipper: MyClipper(),
child: Container(
height: 320,
decoration: BoxDecoration(color: Colors.redAccent),
child: Center(
child: Text(
"bezier",
style: TextStyle(
color: Colors.cyanAccent,
fontSize: 45,
fontWeight: FontWeight.w700),
),
),
),
)
]),
),
);
}
}
class MyClipper extends CustomClipper
{
@override
Path getClip(Size size) {
var path = new Path();
path.lineTo(0, size.height - 80);
var controllPoint = Offset(50, size.height);
var endPoint = Offset(size.width/2, size.height);
path.quadraticBezierTo(controllPoint.dx, controllPoint.dy, endPoint.dx, endPoint.dy);
path.lineTo(size.width, size.height);
path.lineTo(size.width, 0);
return path;
}
@override
bool shouldReclip(CustomClipper oldClipper) {
return true;
}
}
感谢观看,欢迎点赞,评论!!!