Flutter 自定义Widget
一、相应属性介绍
1.CustomPainter介绍
CustomPaint可以称之为动画鼻祖,它可以实现任何酷炫的动画和效果。CustomPaint本身没有动画属性,仅仅是绘制属性,一般情况下,CustomPaint会和动画控制配合使用,达到理想的效果。
CustomerPainter是真实绘制的基础类,需要绘制的图形和画笔都是在此类中实现,一般会自定义一个类继承此基类,然后重写两个方法:
import 'package:flutter/material.dart';
class LCPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
// 绘制
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true; //属性不一致时,需要重新绘制
}
}
2.CustomPaint
CustomPaint 也是一个 Widget。
你可把它嵌到视图树的任意一个节点位置
属性
painter | CustomPainter | 背景画笔,绘制内容会显示在child子节点后面 |
---|---|---|
foregroundPainter | CustomPainter | 前景画笔,绘制内容会显示在child子节点前面 |
size | Size | 设置绘制区域的大小。如果有child,则忽略该参数,且绘制区域为child的尺寸 |
isComplex | bool | 是否复杂的绘制,如果是,Flutter会应用一些缓存策略来减少重复渲染的开销。默认false |
willChange | bool | 和isComplex配合使用,当启用缓存时,该属性代表在下一帧中绘制是否会改变。默认false |
child | Widget | 没错,CustomPaint是可以包含一个子节点的 |
3.Painter绘制属性
canvas中有多个与绘制相关的方法,如drawLine()、drawRect()、drawOval()、drawOval()、等方法。
属性 | 类型 | 说明 |
---|---|---|
isAntiAlias | bool | 是否开启抗锯齿,开启抗锯齿能够是边缘平滑,当然也更消耗系统资源 |
color | Color | 颜色 |
colorFilter | ColorFilter | 会对颜色进行变换 |
filterQuality | FilterQuality | 设置绘制的图像质量 |
invertColors | bool | 是否使用反向颜色。绘制图片时也能够反转图片的颜色 |
maskFilter | MaskFilter | 设置遮罩效果。比如高斯模糊 |
shader | Shader | 渐变颜色。会覆盖color |
strokeCap | StrokeCap | 设置绘制形状的边缘风格。如圆角、方形等 |
strokeJoin | StrokeJoin | 设置两个绘制形状衔接处的风格。如圆角、方形等 |
strokeWidth | double | 画笔的宽度 |
style | PaintingStyle | 填充方式。PaintingStyle.fill-充满;PaintingStyle.stroke-空心 |
blendMode | BlendMode | 像素混合模式。当画一个shape或者合成图层的时候会生效。 |
4.Canvas画布
与Android几乎一摸一样,按照android的自定义View的思想去理解
它包含了很多基础的绘制操作,通过组合这些基础的绘制操作,可以绘制出几乎任何的视图。
Canvas 的操作主要有两类:
- 针对 Canvas 的变换操作,如平移、旋转、缩放、图层等操作。
- 绘制基础图形的操作,如线段、路径、图片、几何图形等。
二、api介绍
1.绘制背景
canvas.drawColor(Colors.orange, BlendMode.srcIn);
2.drawPoints 绘制点/线
PointMode 包括 points 点 / lines 线 / polygon 多边形;注意 lines 为每两点之间的连线,若为奇数个点,最后一个没有与之相连的点
该api与Android不同
class LCPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
// 绘制
canvas.drawColor(Colors.orange, BlendMode.srcIn);
// 绘制点
canvas.drawPoints(
PointMode.points,
[
Offset(30.0, 30.0),
Offset(60.0, 30.0),
Offset(60.0, 60.0),
Offset(30.0, 60.0)
],
Paint()..strokeWidth = 10.0);
canvas.drawPoints(
PointMode.points,
[
Offset(160.0, 30.0),
Offset(190.0, 30.0),
Offset(190.0, 60.0),
Offset(160.0, 60.0)
],
Paint()
..strokeWidth = 10.0
..strokeCap = StrokeCap.round);
// 绘制线
canvas.drawPoints(
PointMode.lines,
[
Offset(30.0, 100.0),
Offset(60.0, 100.0),
Offset(60.0, 130.0),
Offset(30.0, 130.0)
],
Paint()
..strokeWidth = 4.0
..strokeCap = StrokeCap.round);
// 绘制多边形
canvas.drawPoints(
PointMode.polygon,
[
Offset(160.0, 100.0),
Offset(190.0, 100.0),
Offset(190.0, 130.0),
Offset(160.0, 130.0)
],
Paint()
..strokeWidth = 4.0
..strokeCap = StrokeCap.round);
}
3.绘制线
import 'dart:ui';
import 'package:flutter/material.dart';
class LinePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
// 绘制
canvas.drawColor(Colors.orange, BlendMode.srcIn);
// 绘制点
canvas.drawLine(
Offset(30.0, 30.0),
Offset(200 - 30.0, 30.0),
Paint()
..strokeWidth = 10.0
..strokeCap = StrokeCap.butt);
canvas.drawLine(
Offset(30.0, 60.0),
Offset(200 - 30.0, 60.0),
Paint()
..strokeWidth = 10.0
..strokeCap = StrokeCap.round);
canvas.drawLine(
Offset(30.0, 90.0),
Offset(200 - 30.0, 90.0),
Paint()
..strokeWidth = 10.0
..strokeCap = StrokeCap.square);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
4.绘制弧形
属性
void drawArc(Rect rect, //矩形范围,即圆弧所在的圆的范围,若非正方形则圆弧所在的圆会拉伸
double startAngle, //起始角度,0.0 为坐标系 x 轴正向方形
double sweepAngle, //第三个参数为终止角度,若超过 2PI,则为一个圆
bool useCenter,//第四个参数为是否由中心出发,false* 时只绘制圆弧,true 时绘制圆饼,也就是中心点将其连接起来
Paint paint //画笔样式,PaintingStyle.fill,首尾连接起来
) {
1.画弧线 PaintingStyle.stroke
canvas.drawArc(
Rect.fromCircle(center: Offset(50.0, 50.0), radius: 50.0),
0.0,
pi / 2,
false,
Paint()
..color = Colors.white
..strokeCap = StrokeCap.round
..strokeWidth = 6.0
..style = PaintingStyle.stroke);
2.首位连接起来 PaintingStyle.fill
canvas.drawArc(
Rect.fromCircle(center: Offset(150.0, 50.0), radius: 50.0),
0.0,
pi / 2,
false,
Paint()
..color = Colors.white
..strokeWidth = 6.0
..style = PaintingStyle.fill);
3.画饼
canvas.drawArc(
Rect.fromCircle(center: Offset(300.0, 100.0), radius: 50.0),
-pi,
pi / 2,
true,
Paint()
..color = Colors.white
..strokeWidth = 6.0
..style = PaintingStyle.stroke);
4.其他
canvas.drawArc(
Rect.fromLTWH(30.0, 150.0, 80.0, 50.0),
0.0,
pi * 2 * 2 / 3,
true,
Paint()
..color = Colors.white
..style = PaintingStyle.fill);
canvas.drawArc(
Rect.fromLTWH(150.0, 150.0, 80.0, 50.0),
0.0,
pi * 2 * 2 / 3,
true,
Paint()
..color = Colors.white
..strokeWidth = 6.0
..style = PaintingStyle.stroke);
canvas.drawArc(
Rect.fromPoints(Offset(250.0, 150.0), Offset(300.0, 200.0)),
0.0,
5.0,
true,
Paint()
..color = Colors.white
..style = PaintingStyle.fill);
canvas.drawArc(
Rect.fromPoints(Offset(350.0, 150.0), Offset(400.0, 200.0)),
0.0,
5.0,
true,
Paint()
..color = Colors.white
..strokeWidth = 6.0
..style = PaintingStyle.stroke);
5.drawRect 绘制矩形
drawRect 用来绘制矩形,Flutter 提供了多种绘制矩形方法:
Rect.fromPoints 根据两个点(左上角点/右下角点)来绘制;
Rect.fromLTRB 根据以屏幕左上角为坐标系圆点,分别设置上下左右四个方向距离;
Rect.fromLTWH 根据设置左上角的点与矩形宽高来绘制;
Rect.fromCircle 最特殊,根据圆形绘制正方形
canvas.drawRect(
Rect.fromPoints(Offset(30.0, 30.0), Offset(120.0, 60.0)),
Paint()
..color = Colors.white
..strokeWidth = 6.0
..style = PaintingStyle.stroke);
canvas.drawRect(
Rect.fromLTWH(30.0, 140.0, 100.0, 70.0),
Paint()
..color = Colors.white
..strokeWidth = 6.0
..style = PaintingStyle.stroke);
canvas.drawRect(
Rect.fromCircle(center: Offset(200.0, 160.0), radius: 40.0),
Paint()
..color = Colors.white
..strokeWidth = 6.0
..style = PaintingStyle.fill);
6.drawRRect 绘制圆角矩形
- RRect.fromLTRBXY 前四个参数用来绘制矩形位置,剩余两个参数绘制固定 x/y 弧度; x, y弧度
- RRect.fromLTRBR 前四个参数用来绘制矩形位置,最后一个参数绘制 Radius 弧度; 统一弧度
- RRect.fromLTRBAndCorners 前四个参数用来绘制矩形位置,剩余四个可选择参数,根据需求设- - 置四个角 Radius 弧度,可不同; //四个弧度
- RRect.fromRectXY 第一个参数绘制矩形,可以用上面介绍的多种矩形绘制方式,剩余两个参数绘制固定 x/y 弧度;//
- RRect.fromRectAndRadius 第一个参数绘制矩形,可以用上面介绍的多种矩形绘制方式,最后一个参数绘制 Radius 弧度;
- RRect.fromRectAndCorners第一个参数绘制矩形,可以用上面介绍的多种矩形绘制方式,剩余四个可选择参数,根据需求设置四个角 Radius 弧度,最为灵活。
1.两个点fromLTRBXY 和fromLTRBR
canvas.drawRRect(
RRect.fromLTRBXY(120.0, 30.0, 220.0, 80.0, 8.0, 18.0),
Paint()
..color = Colors.white
..style = PaintingStyle.fill);
canvas.drawRRect(
RRect.fromLTRBR(240.0, 30.0, 340.0, 80.0, Radius.circular(8.0)),
Paint()
..color = Colors.white
..strokeWidth = 4.0
..style = PaintingStyle.stroke);
2.两个点设置四个不同圆角 fromLTRBAndCorners
canvas.drawRRect(
RRect.fromLTRBAndCorners(30.0, 120.0, 110.0, 160.0,
topLeft: Radius.circular(20.0),
topRight: Radius.circular(20.0),
bottomRight: Radius.circular(5.0),
bottomLeft: Radius.circular(5.0)),
Paint()
..color = Colors.white
..strokeWidth = 4.0
..style = PaintingStyle.stroke);
3.Rect形式设置
canvas.drawRRect(
RRect.fromRectXY(
Rect.fromCircle(center: Offset(70.0, 240.0), radius: 40.0),
8.0,
8.0),
Paint()
..color = Colors.white
..strokeWidth = 4.0
..style = PaintingStyle.stroke);
7 drawDRRect 绘制嵌套矩形
drawDRRect 绘制嵌套矩形,第一个参数为外部矩形,第二个参数为内部矩形
canvas.drawDRRect(
RRect.fromRectXY(
Rect.fromCircle(center: Offset(90.0, 120.0), radius: 60.0),
8.0,
8.0),
RRect.fromRectXY(
Rect.fromCircle(center: Offset(90.0, 120.0), radius: 50.0),
8.0,
8.0),
Paint()
..color = Colors.white
..strokeWidth = 4.0
..style = PaintingStyle.stroke);
8 绘制圆形
canvas.drawCircle(
Offset(90.0, 120.0),
60.0,
Paint()
..color = Colors.white
..strokeWidth = 4.0
..style = PaintingStyle.stroke);
canvas.drawCircle(
Offset(270.0, 120.0),
60.0,
Paint()
..color = Colors.white
..style = PaintingStyle.fill);
9drawOval 绘制椭圆
canvas.drawOval(
Rect.fromLTRB(30.0, 30.0, 150.0, 80.0),
Paint()
..color = Colors.white
..strokeWidth = 3.0
..style = PaintingStyle.stroke);
canvas.drawOval(
Rect.fromLTRB(210.0, 30.0, 330.0, 80.0),
Paint()
..color = Colors.white
..strokeWidth = 3.0
..style = PaintingStyle.fill);
10path绘制路径,与Android api一致
- moveTo() 即从当前坐标点开始,不设置时默认为屏幕左上角位置;
- lineTo() 即从起点绘制到设置的新的点位;
- close() 即最后的点到起始点连接,但对于中间绘制矩形/弧等时最后不会相连;
- reset() 即清空连线;
- addRect() 添加矩形连线;
- addOval() 添加弧线,即贝塞尔(二阶)曲线;
- cubicTo() 添加弧线,即贝塞尔(三阶)曲线;
- arcTo() 画弧线
- relativeMoveTo() 相对于移动到当前点位,小菜认为与 moveTo 相比整个坐标系移动;
- relativeLineTo() 相对连接到当前点位,并将坐标系移动到当前点位
1.第一种
canvas.drawPath(
Path()
..moveTo(170.0, 30.0)
..lineTo(220.0, 30.0)
..lineTo(170.0, 80.0)
..lineTo(220.0, 80.0)
..close(),
Paint()
..color = Colors.white
..strokeWidth = 3.0
..style = PaintingStyle.stroke);
2.第二种,close遇到中间绘制矩形/弧等无法关闭
canvas.drawPath(
Path()
..moveTo(30.0, 120.0)
..lineTo(120.0, 120.0)
..lineTo(30.0, 160.0)
..lineTo(120.0, 160.0)
..addRect(Rect.fromLTWH(180.0, 130.0, 120.0, 70.0))
..addOval(Rect.fromLTWH(190.0, 140.0, 100.0, 50.0))
..moveTo(30.0, 230.0)
..lineTo(160.0, 230.0)
..close(),
Paint()
..color = Colors.white
..strokeWidth = 3.0
..style = PaintingStyle.stroke);
3.画弧线arcTo
canvas.drawPath(
Path()
..arcTo(Rect.fromCircle(center: Offset(70, 270), radius: 50), 0.0, pi,
false),
Paint()
..color = Colors.white
..strokeWidth = 3.0
..style = PaintingStyle.stroke);
11绘制阴影drawShadow
drawShadow 用于绘制阴影,第一个参数时绘制一个图形 Path,第二个是设置阴影颜色,第三个为阴影范围,最后一个阴影范围是否填充满;
canvas.drawShadow(
Path()
..moveTo(30.0, 30.0)
..lineTo(120.0, 30.0)
..lineTo(120.0, 60.0)
..lineTo(30.0, 60.0)
..close(),
Colors.red,
4,
false);
12drawImage 绘制图片
drawImage 用于绘制图片,绘制图片是重点,此时的 Image 并非日常所用的图片加载,而是用的 dart.ui 类中的 ui.Image 并转换成字节流 ImageStream 方式传递,包括本地图片或网络图片
import 'dart:async';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: DrawImagePage());
}
}
class DrawImagePage extends StatefulWidget {
DrawImagePage({Key key}) : super(key: key);
@override
_DrawImagePageState createState() => _DrawImagePageState();
}
class _DrawImagePageState extends State<DrawImagePage> {
ui.Image _image1;
ui.Image _image2;
@override
void initState() {
super.initState();
_prepareImg();
}
@override
Widget build(BuildContext context) {
var winH = MediaQuery.of(context).size.height;
var winW = MediaQuery.of(context).size.width;
return Scaffold(
appBar: AppBar(),
body: CustomPaint(
size: Size(winW, winH),
painter: LCPainter(_image1, _image2),
),
);
}
Future<ui.Image> load(String asset) async {
ByteData data = await rootBundle.load(asset);
ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
ui.FrameInfo fi = await codec.getNextFrame();
return fi.image;
}
// 获取图片 本地为false 网络为true
Future<ui.Image> _loadImage(var path, bool isUrl) async {
ImageStream stream;
if (isUrl) {
stream = NetworkImage(path).resolve(ImageConfiguration.empty);
} else {
stream = AssetImage(path, bundle: rootBundle)
.resolve(ImageConfiguration.empty);
}
Completer<ui.Image> completer = Completer<ui.Image>();
var listener = ImageStreamListener((ImageInfo info, bool syncCall) {
final ui.Image image = info.image;
completer.complete(image);
// stream.removeListener(listener);
});
stream.addListener(listener);
return completer.future;
}
var _value = 0;
// 加载图片
_prepareImg() {
_loadImage('images/a.jpeg', false).then((image1) {
setState(() {
_image1 = image1;
print("xxxxxxx1");
});
}).whenComplete(() {
_loadImage('https://www.itying.com/images/flutter/3.png', true)
.then((image2) {
setState(() {
_image2 = image2;
});
}).whenComplete(() {
if (this.mounted) {
setState(() {});
}
});
});
}
}
class LCPainter extends CustomPainter {
final ui.Image image1;
final ui.Image image2;
LCPainter(this.image1, this.image2);
@override
void paint(Canvas canvas, Size size) {
print("xxxxxxx3");
canvas.drawColor(Colors.orange, BlendMode.srcIn);
if (image1 != null) {
canvas.drawImage(image1, Offset(20.0, 240.0), Paint());
}
if (image2 != null) {
canvas.drawImage(image2, Offset(60.0, 60.0), Paint());
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
参考文章:
原文链接:https://blog.csdn.net/tianzhilan0/article/details/107692508