效果
功能
- 支持绘制线、圆、矩形,支持拓展
- 支持撤回上一步
- 支持清空画板
- 支持自定义画笔颜色,宽度
实现
定义绘制类型
/// 类型
enum ShapeType {
//线
line,
//圆
circle,
//矩形
rectangle,
//拓展
}
定义绘制抽象类
import 'dart:ui';
/// 绘制抽象类
abstract class Shape {
void draw(
Canvas canvas,
List<Offset> points,
Paint paint,
);
}
实现线、圆、矩形绘制
/// 绘制圆
class CircleShape implements Shape {
@override
void draw(Canvas canvas, List<Offset> points, Paint paint) {
if (points.length >= 2) {
double radius = (points[points.length - 1] - points[1]).distance / 2;
paint.style = PaintingStyle.stroke;
canvas.drawCircle(points[0], radius, paint);
}
}
}
/// 绘制线
class LineShape implements Shape {
@override
void draw(Canvas canvas, List<Offset> points, Paint paint) {
for (int i = 0; i < points.length - 1; i++) {
canvas.drawLine(points[i], points[i + 1], paint);
}
}
}
/// 绘制方
class RectangleShape implements Shape {
@override
void draw(Canvas canvas, List<Offset> points, Paint paint) {
if (points.length >= 2) {
final rect = Rect.fromPoints(points[0], points[points.length - 1]);
paint.style = PaintingStyle.stroke;
canvas.drawRect(rect, paint);
}
}
}
定义工厂类 factory
/// 根据绘制类型返回具体绘制对象
Shape getShape(ShapeType type) {
switch (type) {
case ShapeType.line:
return LineShape();
case ShapeType.circle:
return CircleShape();
case ShapeType.rectangle:
return RectangleShape();
}
}
定义笔画参数对象
/// 笔画参数对象
class DrawingStroke {
Color color;
double width;
List<Offset> points;
ShapeType type;
DrawingStroke({
this.color = Colors.black,
this.width = 2.0,
this.points = const [],
this.type = ShapeType.line,
});
}
定义绘制控制器
/// 绘制控制器
class DrawingController {
final _strokes = <DrawingStroke>[];
final _listeners = <VoidCallback>[];
// 所有绘制笔画数据
List<DrawingStroke> get strokes => List.unmodifiable(_strokes);
// 画笔颜色
Color selectedColor = Colors.black;
// 画笔宽度
double strokeWidth = 2.0;
// 绘制类型
ShapeType selectedType = ShapeType.line;
// 开始绘制
void startDrawing(Offset point) {
_strokes.add(DrawingStroke(
color: selectedColor,
width: strokeWidth,
points: [point],
type: selectedType,
));
_notifyListeners();
}
// 正在绘制
void updateDrawing(Offset point) {
if (_strokes.isNotEmpty) {
_strokes.last.points.add(point);
_notifyListeners();
}
}
// 结束当前笔画绘制
void endDrawing() {
_notifyListeners();
}
// 撤回一笔
void undo() {
if (_strokes.isNotEmpty) {
_strokes.removeLast();
_notifyListeners();
}
}
// 清空数据
void clear() {
_strokes.clear();
_notifyListeners();
}
// 设置画笔颜色
void setColor(Color color) {
selectedColor = color;
_notifyListeners();
}
// 设置画笔宽度
void setStrokeWidth(double width) {
strokeWidth = width;
_notifyListeners();
}
// 设置绘制类型
void setDrawingType(ShapeType type) {
selectedType = type;
_notifyListeners();
}
void _notifyListeners() {
for (var listener in _listeners) {
listener();
}
}
void addListener(VoidCallback listener) {
_listeners.add(listener);
}
void removeListener(VoidCallback listener) {
_listeners.remove(listener);
}
}
定义画板类DrawingBoard
class DrawingBoard extends StatefulWidget {
final DrawingController controller;
const DrawingBoard({Key? key, required this.controller}) : super(key: key);
@override
State<StatefulWidget> createState() => DrawingBoardState();
}
class DrawingBoardState extends State<DrawingBoard> {
@override
void initState() {
super.initState();
widget.controller.addListener(_updateState);
}
void _updateState() {
setState(() {});
}
@override
void dispose() {
super.dispose();
widget.controller.removeListener(_updateState);
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, size) {
return SizedBox(
width: size.maxWidth,
height: size.maxHeight,
child: GestureDetector(
onPanStart: (details) {
widget.controller.startDrawing(details.localPosition);
},
onPanUpdate: (details) {
widget.controller.updateDrawing(details.localPosition);
},
onPanEnd: (_) {
widget.controller.endDrawing();
},
child: CustomPaint(
painter: DrawingPainter(strokes: widget.controller.strokes),
size: Size.infinite,
),
),
);
});
}
}
class DrawingPainter extends CustomPainter {
final Paint drawPaint = Paint();
DrawingPainter({required this.strokes});
List<DrawingStroke> strokes;
@override
void paint(Canvas canvas, Size size) {
for (var stroke in strokes) {
drawPaint
..color = stroke.color
..strokeCap = StrokeCap.round
..strokeWidth = stroke.width;
Shape shape = getShape(stroke.type);
shape.draw(canvas, stroke.points, drawPaint);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
使用画板
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_xy/xydemo/drawingboard/drawing/type.dart';
import 'package:flutter_xy/xydemo/drawingboard/drawing/view.dart';
import 'drawing/controller.dart';
class DrawingPage extends StatefulWidget {
const DrawingPage({Key? key}) : super(key: key);
@override
DrawingPageState createState() => DrawingPageState();
}
class DrawingPageState extends State<DrawingPage> {
final _controller = DrawingController();
@override
void initState() {
super.initState();
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
}
@override
void dispose() {
super.dispose();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Row(
children: [
SizedBox(
width: 120,
child: ListView(
scrollDirection: Axis.vertical,
padding: const EdgeInsets.symmetric(horizontal: 20),
children: [
const SizedBox(width: 10),
_buildText("操作"),
const SizedBox(width: 10),
_buildButton('Undo', () => _controller.undo()),
const SizedBox(width: 10),
_buildButton('Clear', () => _controller.clear()),
const SizedBox(width: 10),
_buildText("画笔颜色"),
const SizedBox(width: 10),
_buildColorButton(Colors.red),
const SizedBox(width: 10),
_buildColorButton(Colors.blue),
const SizedBox(width: 10),
_buildText("画笔宽度"),
const SizedBox(width: 10),
_buildStrokeWidthButton(2.0),
const SizedBox(width: 10),
_buildStrokeWidthButton(5.0),
const SizedBox(width: 10),
_buildText("画笔类型"),
const SizedBox(width: 10),
_buildTypeButton(ShapeType.line, '线'),
const SizedBox(width: 10),
_buildTypeButton(ShapeType.circle, '圆'),
const SizedBox(width: 10),
_buildTypeButton(ShapeType.rectangle, '方'),
],
),
),
Expanded(
child: Column(
children: [
Expanded(
child: DrawingBoard(
controller: _controller,
),
),
],
),
),
],
),
),
);
}
Widget _buildText(String text) {
return Text(
text,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
),
);
}
Widget _buildButton(String text, VoidCallback onPressed) {
return ElevatedButton(
onPressed: onPressed,
child: Text(text),
);
}
Widget _buildColorButton(Color color) {
return ElevatedButton(
onPressed: () => _controller.setColor(color),
style: ElevatedButton.styleFrom(primary: color),
child: const SizedBox(width: 30, height: 30),
);
}
Widget _buildStrokeWidthButton(double width) {
return ElevatedButton(
onPressed: () => _controller.setStrokeWidth(width),
child: Text(width.toString()),
);
}
Widget _buildTypeButton(ShapeType type, String label) {
return ElevatedButton(
onPressed: () => _controller.setDrawingType(type),
child: Text(label),
);
}
}
运行效果如下图:
详情见 github.com/yixiaolunhui/flutter_xy