「时光不负,创作不停,本文正在参加2022年中总结征文大赛」
先上效果图:
流程:
- 继承CustomPainter自定义画笔核心---BoardPainter
- 使用Listener监听滑动手势将移动的位置点收集起来_pointOffset
- 使用画布的canvas.drawLine方法将所有点连接起来绘制出线条
- 采用RepaintBoundary包裹CustomPaint隔离画笔控件,防止singlescrollview 滑动发生重绘,导致画笔paint响应
- 使用_HitTestBlocker使事件可以穿透,使画板下的控件可以参与命中测试,防止点击事件无效比如:TextButton的点击事件被上面消耗(
hitTest()事件)
一、继承CustomPainter自定义画笔核心
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import '../Gesture/GestureDetectorHitTest.dart';
///组合动画用法
///
///抽奖滚动动画 stack的组合
///
///
///
///
class PointOffset {
Offset point;
bool isEnd;
PointOffset.name(this.point, this.isEnd);
}
class CanvasPointScroll extends StatefulWidget {
@override
_CanvasPointScrollState createState() => _CanvasPointScrollState();
}
class _CanvasPointScrollState extends State<CanvasPointScroll> {
//布局
final GlobalKey containWidgetGlobalKey = GlobalKey();
var _scrollController = ScrollController();
//画布
late BoardPainterWidget canvasWidget;
//局部刷新
late CounterNotify counterNotify;
bool isScroll = true;
@override
void initState() {
super.initState();
counterNotify = CounterNotify();
Future.delayed(Duration(milliseconds: 5000), () {
counterNotify.addValue(400);
});
canvasWidget = BoardPainterWidget(containWidgetGlobalKey);
}
@override
Widget build(BuildContext context) {
return Material(
child: Center(
child: SingleChildScrollView(
physics: isScroll?null:NeverScrollableScrollPhysics(),
controller: _scrollController,
child: Stack(
children: [
ListView(
shrinkWrap: true,
physics: new NeverScrollableScrollPhysics(),
key: containWidgetGlobalKey,
children: [
Column(
children: [
Container(
color: Color(0xFF15C9E8),
width: 100,
height: 400,
),
Container(
color: Color(0xFF300C92),
width: 400,
height: 400,
),
Container(
color: Color(0xFFB41C89),
width: 400,
height: 400,
),
TextButton(
onPressed: () {
print("按钮");
isScroll = !isScroll;
setState(() {
});
},
child: Text("data")),
TextField(),
ValueListenableBuilder<double>(
builder: (BuildContext context, value, Widget? child) {
return Container(
color: Color(0xFF1947EE),
width: 400,
height: value,
);
},
valueListenable: counterNotify.countListenable,
)
],
),
],
),
canvasWidget
],
),
),
),
);
}
}
class CounterNotify with ChangeNotifier {
ValueNotifier<double> _countListenable = ValueNotifier<double>(0.0);
ValueNotifier<double> get countListenable => _countListenable;
void addValue(double i) {
_countListenable.value += i;
}
}
class BoardPainterWidget extends StatefulWidget {
GlobalKey containWidgetGlobalKey;
BoardPainterWidget(this.containWidgetGlobalKey);
// void Function(double move)? callMoveBack;
// void getMoveDistance(double move){
// print("getMoveDistance--:$move");
// _state.getMoveDistance(move);
// }
_BoardPainterWidgetState _state = _BoardPainterWidgetState();
@override
_BoardPainterWidgetState createState() {
return _state;
}
}
class _BoardPainterWidgetState extends State<BoardPainterWidget> {
/// 已描绘的点
List<PointOffset> _pointOffset = <PointOffset>[];
final GlobalKey containWidgetGlobalKey = GlobalKey();
//一屏的高度
double screenHeight = 0;
//画布的高度
double _gestureDetectorHeight = 0;
double _gestureDetectorWidth = 0;
late ValueNotifier<List<PointOffset>> _pointListenable;
/// 添加点,注意不要超过Widget范围
_addPoint(PointerMoveEvent details) {
Size? referenceBox = widget.containWidgetGlobalKey.currentContext?.size;
double maxW = referenceBox?.width ?? 0;
double maxH = referenceBox?.height ?? 0;
// 校验范围
if (details.localPosition.dx <= 0 || details.localPosition.dy <= 0) return;
if (details.localPosition.dx > maxW || details.localPosition.dy > maxH)
return;
// print("_addPoint $_gestureDetectorHeight");
// setState(() {
// _pointOffset = List.from(_pointOffset)
// ..add(PointOffset.name(details.localPosition, true));
// });
//listener监听,不会影响整棵树的绘制
_pointOffset = List.from(_pointOffset)
..add(PointOffset.name(details.localPosition, true));
_pointListenable.value = _pointOffset;
}
void moveCallback(double move) {}
@override
void initState() {
super.initState();
_pointListenable = ValueNotifier<List<PointOffset>>(_pointOffset);
WidgetsBinding.instance?.addPostFrameCallback((_) {
_gestureDetectorHeight =
widget.containWidgetGlobalKey.currentContext?.size?.height ?? 0;
_gestureDetectorWidth =
widget.containWidgetGlobalKey.currentContext?.size?.width ?? 0;
print("绘制完成1:$_gestureDetectorHeight");
WidgetsBinding.instance?.addPersistentFrameCallback((_) {
print("实时Frame绘制回调"); //每帧都回调
var widgetHeight =
widget.containWidgetGlobalKey.currentContext?.size?.height ?? 0;
if (widgetHeight != _gestureDetectorHeight &&
widgetHeight > _gestureDetectorHeight) {
_gestureDetectorHeight = widgetHeight;
_gestureDetectorWidth =
widget.containWidgetGlobalKey.currentContext?.size?.width ?? 0;
setState(() {});
print("绘制完成2:$_gestureDetectorHeight");
}
});
setState(() {});
});
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
_HitTestBlocker(child: Listener(
onPointerMove: (details) {
print(
"object:${details.localPosition}----${details.distance}");
_addPoint(details);
},
onPointerUp: (details) {
_pointOffset.add(PointOffset.name(Offset.zero, false));
},
child: Container(
height: _gestureDetectorHeight,
width: _gestureDetectorWidth,
color: Color(0xFFFFFF),
))),
RepaintBoundary(
//隔离重绘控件,singlescrollview 滑动发生重绘,导致画笔paint响应
child: CustomPaint(
painter: BoardPainter(
points: _pointOffset, factor: _pointListenable))),
],
);
}
}
class BoardPainter extends CustomPainter {
final ValueNotifier<List<PointOffset>>? factor;
BoardPainter({required this.points, this.factor}) : super(repaint: factor);
List<PointOffset> points;
void paint(Canvas canvas, Size size) {
points = factor?.value ?? points;
print("绘制 paint1 ${points.length}-- ${factor?.value.length}");
if (points == null) {
return;
}
Paint paint = Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0;
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null &&
points[i + 1] != null &&
points[i + 1].isEnd &&
points[i].isEnd) {
// print("绘制 paint${points[i].point}----${points[i + 1].point}");
canvas.drawLine(points[i].point, points[i + 1].point, paint);
}
}
}
bool shouldRepaint(BoardPainter other) {
// print("绘制 shouldRepaint--:${other.points != points}");
return other.points != points;
}
}
class _HitTestBlocker extends SingleChildRenderObjectWidget {
_HitTestBlocker({
Key? key,
this.blocker = true,
Widget? child,
}) : super(key: key, child: child);
final bool blocker;
@override
RenderObject createRenderObject(BuildContext context) {
return _RenderHitTestBlocker(blocker: blocker);
}
@override
void updateRenderObject(
BuildContext context, _RenderHitTestBlocker renderObject) {
renderObject..blocker = blocker;
}
}
class _RenderHitTestBlocker extends RenderProxyBox {
_RenderHitTestBlocker({this.blocker = true});
bool blocker;
@override
bool hitTest(BoxHitTestResult result, {required Offset position}) {
if (blocker) {
hitTestChildren(result, position: position);
return false;
} else {
return super.hitTest(result, position: position);
}
}
@override
bool hitTestSelf(Offset position) => blocker;
}