If you're writing a game, I would highly recommend using a framework for that because it will take care of all sorts of things like this.
However, I appreciate that you're trying to understand how that would be done - that's an admirable goal so I'll do my best to explain.
Basically, you need to hook into flutter's rendering and change how it's done, as flutter optimizes a lot of things that shouldn't necessarily be optimized for a game.
Returning true from shouldRepaint
is a good first step, but you actually have to do a lot more than that. In a normal application, your CustomPainter object should be a constant object which is created for each time there is a change. Normally you'd use something like an AnimationBuilder and create a new custompainter with updated values each time. And then in the custompainter's shouldRepaint, you'd compare the old custompainter's values with the new ones and redraw only if they're different.
For a very simple game, you may be able to get away with using a few animations, along with flutter's drag & drop, and buttons etc. But anything at all complicated would probably be better done in something more custom and more tuned to games.
To understand how that would be done, I'd recommend reading Flame's source code. Flame is a lightweight game engine that runs on flutter. It actually abstracts the drawing away from flutter and instead has a bunch of game 'components' which each do their own painting. See Flame's game.dart file as it is where this logic is.
Some of the relevant code, from the GameRenderBox which inherits RenderBox :
void _scheduleTick() {
_frameCallbackId = SchedulerBinding.instance.scheduleFrameCallback(_tick);
}
void _unscheduleTick() {
SchedulerBinding.instance.cancelFrameCallbackWithId(_frameCallbackId);
}
void _tick(Duration timestamp) {
if (!attached) {
return;
}
_scheduleTick();
_update(timestamp);
markNeedsPaint();
}
_tick is called each and every frame, and then reschedules itself for during the next frame.
The RenderBox's render function is overriden:
@override
void paint(PaintingContext context, Offset offset) {
context.canvas.save();
context.canvas.translate(
game.builder.offset.dx + offset.dx, game.builder.offset.dy + offset.dy);
game.render(context.canvas);
context.canvas.restore();
}
And that calls the game's render function, which in turn tells each of its components to render:
void render(Canvas canvas) {
canvas.save();
components.forEach((comp) => renderComponent(canvas, comp));
canvas.restore();
}
This is more in line with how games normally do things.