Flutter保留背景画布绘制,以便在只有前景更改时跳过重新绘制



我正在做一个项目,该项目有一个CustomPaint,它可以绘制形状作为背景,当你点击屏幕时,它会在特定位置绘制一个圆圈。我使用GestureDetector来获取抽头数据,并将其作为参数发送给_MyCustomPainter类似于下面的代码:

class MyApp extends StatefulWidget{
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Offset _circlePosition = Offset(-1, -1);
void setCirclePosition(Offset newPosition) {
setState(() {
this._circlePosition = newPosition;
});
}
void clearCircle() {
setState(() {
this._circlePosition = Offset(-1, -1);
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
backgroundColor: Colors.green,
title: Text('My app'),
),
body: Container(
child: GestureDetector(
onTapDown: (detail) {
setCirclePosition(detail.localPosition);
},
onHorizontalDragStart: (detail) {
setCirclePosition(detail.localPosition);
},
onHorizontalDragUpdate: (detail) {
setCirclePosition(detail.localPosition);
},
onVerticalDragStart: (detail) {
setCirclePosition(detail.localPosition);
},
onVerticalDragUpdate: (detail) {
setCirclePosition(detail.localPosition);
},
onTapUp: (detail) {
clearCircle();
},
onVerticalDragEnd: (detail) {
clearCircle();
},
onHorizontalDragEnd: (detail) {
clearCircle();
},
child: LimitedBox(
maxHeight: 400,
maxWidth: 300,
child: CustomPaint(
size: Size.infinite,
painter: new _MyCustomPainter(
circlePosition: circlePosition,
),
),
),
),
),
),
);
}
}
class _MyCustomPainter extends CustomPainter {
_MyCustomPainter({
this.circlePosition,
});
final Offset circlePosition;
void _drawBackground(Canvas canvas) {
// draw the background
}
@override
void paint(Canvas canvas, Size size) {
_drawBackground(canvas);
if (circlePosition.dx != -1 && circlePosition.dy != -1) {
// draws a circle on the position
var circlePaint = Paint()..color = Colors.green;
canvas.drawCircle(circlePosition, 5, circlePaint);
}
}
@override
bool shouldRepaint(_MyCustomPainter old) {
return circlePosition.dx != old.circlePosition.dx ||
circlePosition.dy != old.circlePosition.dy;
}
}

所以这里的问题是,每次用户在屏幕上长按手指时,没有改变的背景都会被一遍又一遍地重新绘制,绘制这个特定的背景需要很多代码。有shouldRepaint方法,但它发出重新绘制整个小部件的信号。我怎么能让背景只画一次,而在重新绘制时,我只使用以前创建的背景?使用PictureRecorder生成图像并在未来重新绘制时使用该图像是最好的方法吗?我的CustomPainter是否应该扩展ChangeNotifier以获得更好的性能?目前的方法有效,但我想知道如何改进它

所以我用来实现这一点的方法是拥有两个CustomPainter,一个用于背景(我们称之为BackgroundPainter(,一个用于前景(就像ForegroundPainter一样,当BackgroundPaiter.sshouldRepaint方法返回true时,你可以在画布上绘制它并将其保存到图像中,然后在ForegroundPaint上使用该图像。我不得不在这里为这个蜡烛图小部件做这件事。代码的简短版本类似于下面的示例:

class MyWidget extends StatefulWidget {
MyWidget({
this.foregroundParam,
this.backgroundParam,
});
final String foregroundParam;
final String backgroundParam;
@override
MyWidget createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Picture backgroundPicture;
_BackgroundPainter backgroundPainter;
Size parentSize;
Size oldParentSize;

@override
Widget Build(BuildContext context) {
// We need the context size, which is only available after
// the build method finishes.
WidgetsBinding.instance.addPostFrameCallback((_) {
// this code only runs after build method is run
if (parentSize != context.size) {
setState(() {
parentSize = context.size;
});
}
});

var newBackgroundPainter = _BackgroundPainter(
backgroundParam: widget.backgroundParam,
);

// update backgroundPicture and backgroundPainter if parantSize was updated
// (like after a screen rotation) or if backgroundPainter was updated
// based on the backgroundParam
if (parentSize != null &&
(oldParentSize != null && oldParentSize != parentSize || 
backgroundPainter == null || 
newBackgroundPainter.shouldRepaint(backgroundPainter) ||
backgroundPicture == null)
) {
oldParentSize = parentSize;
backgroundPainter = newBackgroundPainter;
var recorder = PictureRecorder();
var canvas = Canvas(recorder, Rect.fromLTWH(0, 0, parentSize.width, parentSize.height));
backgroundPainter.paint(canvas, parentSize);
setState(() {
backgroundPicture = recorder.endRecording();
});
}
// if there is no backgroundPicture, this must be the first run where
// parentSize was not set yet, so return a loading screen instead, but
// this is very quick and most likely will not even be noticed. Could return
// an empty Container as well
if (backgroundPicture == null) {
return Container(
width: double.infinity,
height: double.infinity,
child: Center(
child: CircularProgressIndicator(),
),
);
}
// if everything is set, return an instance of _ForegroundPainter passing
// the backgroundPicture as parameter
return CustomPaint(
size: Size.infinite,
painter: _ForegroundPainter(
backgroundPicture: backgroundPicture,
foregroundParam: widget.foregroundParam,
),
);
}
}
class _BackgroundPainter extends CustomPainter {
_BackgroundPainter({
this.backgroundParam,
});
final String backgroundParam;
@override
void paint(Canvas canvas, Size size) {
// paint background here
}
@override
bool shouldRepaint(_BackgroundPainter oldPainter) {
return backgroundParam != oldPainter.backgroundParam;    
}
}
class _ForegroundPainter extends CustomPainter {
_ForegroundPainter({
this.backgroundPicture,
this.foregroundParam,
});
final Picture backgroundPicture;
final String foregroundParam;
@override
void paint(Canvas canvas, Size size) {
canvas.drawPicture(backgroundPicture);
// paint foreground here
}
@override
bool shouldRepaint(_ForegroundPainter oldPainter) {
return foregroundParam != oldPainter.foregroundParam ||
backgroundPicture != oldPainter.backgroundPicture;   
}
}

这很有效,但如果您实现了橡皮擦,它很快就会变得合适。

最新更新