点击“奇舞移动技术”关注我们!
内容简介
Flutter Team 已经提供了很多的 widget,风格也不同,比如 Android 的 Material Design 、iOS 的 Cupertino 等等。在大多数情况下,是可以满足基本需求的。但是遇到高度定制化的需求,就难以满足,好在自定义的方式可以满足我们。
Widget 呈现方式
1、组合方式
我们一般实现的布局方式,基本都是排列组合的方式完成的布局组件树,来呈现布局。
这种方式的使用,基本上是复杂度较高的组件,比如数据可视化需求的南丁格尔玫瑰图,饼状图,多边形等等。
怎样自绘制?
Flutter “毫无创新”地给出类似于很多2D UI系统的画布+画笔的方式完成自定义绘制。具体涉及到的类有:Canvas、Paint、CustomPainter。自定义 CustomPainter 的类,直接看一下代码,如下:
List _points = [];
class MyCustomPainter extends CustomPainter {
MyCustomPainter(this.points);
final List points;
Paint _paint = new Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..isAntiAlias = true
..strokeWidth = 7.0
..strokeJoin = StrokeJoin.bevel;
@override
void paint(Canvas canvas, Size size) {
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null) {
canvas.drawLine(points[i], points[i + 1], _paint);
}
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
重写了两个方法:
1、paint(Canvas canvas, Size size) 绘制的具体操作在这里执行;
2、shouldRepaint 表示当前的 widget 绘制完成后就不需要重新绘制了,自定义组件返回 ture。
利器介绍
画笔 Paint
Paint _paint = new Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..isAntiAlias = true
..strokeWidth = 7.0;
1、color 画笔颜色属性
2、strokeCap 画笔开始与结束,有圆头
3、isAntiAlias 是否抗锯齿
4、strokeWidth 画笔粗细
其实,画笔属性还有很多,请移步到 painting.dart 查看。
painting.dart
https://github.com/flutter/engine/blob/master/lib/ui/painting.dart
画布 Canvas
1、画点
void drawPoints(PointMode pointMode, List points, Paint paint)
需要 PointMode 是一枚举类型和 point 集合。
PointMode 有三个类型:
points(每个点)、
lines:把两个点组成的序列画成线段。如果points数量是奇数,最后的点就会被忽略掉、
polygon 把点的整个序列画成一条线。
2、画线
void drawLine(Offset p1, Offset p2, Paint paint)
使用给定的 Paint 在给定的点之间画一条线。“p1”和“p2”参数是相对于原点的偏移量。
3、画圆
void drawCircle(Offset c, double radius, Paint paint)
圆心 offset ,半径 radius,设置paint的填充模式可以绘制填充和不填充的圆。
4、画椭圆
void drawOval(rect, paint)
矩形 Rect 来确定大小和位置。用 paint 绘制一个轴向对齐的椭圆。椭圆形是填充还是描边(或两者都是)由[Paint.style]控制。
5、画圆弧
void drawArc(rect, startAngle, sweepAngle, useCenter, paint)
矩形 Rect 来确定大小和位置, startAngle 开始弧度, sweepAngle 弧度大小, useCenter 是否使用中心点绘
Android绘制 startAngle 和 sweepAngle 使用的是角度,这里使用的弧度,角度和弧度的换算公式:
弧度 = 角度 * PI/180;
角度 = 弧度 * 180/PI;
度 | 弧度 |
---|---|
0° | 0 |
30° | π/6 |
45° | π/4 |
60° | π/3 |
90° | π/2 |
120° | 2π/3 |
180° | π |
270° | 3π/2 |
360° | 2π |
6、画圆角矩形
void drawRRect(RRect rrect, Paint paint)
用 paint 绘制一个圆角矩形。矩形是被填充还是被描边(或者两者都是)是由[Paint.style]控制的。
其实,画布的用法还有很多,请移步到 Canvas 。
Canvas
https://github.com/flutter/engine/blob/master/lib/ui/painting.dart
画了这么多,耳边想起一首热门歌曲《野狼disco》,
“
全场动作必须跟我整齐划一
来 左边儿 跟我一起画个龙
在你右边儿 画一道彩虹(走起)
来 左边儿 跟我一起画彩虹
在你右边儿 再画个龙(别停)
”
野狼disco
https://music.163.com/#/song?id=1357785909
手势识别 GestureDetector
我们实现手写板涂鸦,字签名功能时,就需要用到这个组件,用来获取到坐标系上的点位Offset。这些收集到的Offset正是Canvas需要的参数,paint需要绘制的点位。另外的场景,比如自定义widget可以根据手势进行交互等。
简单使用代码:
GestureDetector(
onTap: () {
setState(() { _lights = true; });
},
child: Container(
color: Colors.yellow,
child: Text('TURN LIGHTS ON'),
),
)
在Demo实例中实现了手写板,签上自己的大名,具体的实现如下:
class SignaturePainter extends CustomPainter {
SignaturePainter(this.points);
final List points;
Paint _paint = new Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..isAntiAlias = true
..strokeWidth = 7.0
..strokeJoin = StrokeJoin.bevel;
@override
void paint(Canvas canvas, Size size) {
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null) {
canvas.drawLine(points[i], points[i + 1], _paint);
}
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
class Signature extends StatefulWidget {
SignatureState createState() => new SignatureState();
}
List _points;
class SignatureState extends State {
_requestPermission() async {
await PermissionHandler().requestPermissions([PermissionGroup.storage]);
}
@override
void initState() {
super.initState();
_points = [];
}
@override
void dispose() {
_points = null;
super.dispose();
}
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
final width = size.width;
return Stack(
children: [
GestureDetector(
onPanUpdate: (DragUpdateDetails details) {
RenderBox referenceBox = context.findRenderObject();
Offset localPosition =
referenceBox.globalToLocal(details.globalPosition);//坐标系转化,将给定的点从逻辑像素的全局坐标系统转换为此框的本地坐标系统。
if (localPosition.dx > 2 &&
localPosition.dx < (width - (margin * 2)) &&
localPosition.dy > 2 &&
localPosition.dy < height) {
setState(() {
_points = new List.from(_points)..add(localPosition);
});
}
},
onPanEnd: (DragEndDetails details) => _points.add(null),
),
CustomPaint(
painter: SignaturePainter(_points),
),
],
);
}
}
效果图
推荐阅读
--END--
识别二维码,关注我们