2017-10-12 9 views
2

j'ai une application qui affiche un point noir au point où l'utilisateur touche l'écran comme ceci:Comment m'assurer que ma peinture de widget CustomPaint est stockée dans le cache raster?

enter image description here

Le point noir peut être déplacé par l'utilisateur comme il/elle tire son doigt sur la écran. L'arrière-plan est une opération de peinture coûteuse, j'ai donc créé deux widgets séparés dans une pile, en espérant que la peinture du widget d'arrière-plan sera stockée dans le cache raster de Flutter. Mais ce n'est pas stocké - Flutter appelle ma méthode de peinture coûteuse chaque fois que le point noir se déplace.

Qu'est-ce que je fais mal?

Voici mon code:

import 'package:flutter/material.dart'; 
import 'dart:math'; 

void main() { 
    runApp(new MyApp()); 
} 

class MyApp extends StatelessWidget { 
    @override 
    Widget build(BuildContext context) { 
    return new MaterialApp(
     home: new MyHomePage(), 
    ); 
    } 
} 

class MyHomePage extends StatefulWidget { 
    @override 
    State createState() => new MyHomePageState(); 
} 

class MyHomePageState extends State<MyHomePage> { 
    GlobalKey _paintKey = new GlobalKey(); 
    Offset _offset; 

    @override 
    Widget build(BuildContext context) { 
    return new Scaffold(
     body: new Stack(
     fit: StackFit.expand, 
     children: <Widget>[ 
      new CustomPaint(
      painter: new ExpensivePainter(), 
      isComplex: true, 
      willChange: false, 
     ), 
      new Listener(
      onPointerDown: _updateOffset, 
      onPointerMove: _updateOffset, 
      child: new CustomPaint(
       key: _paintKey, 
       painter: new MyCustomPainter(_offset), 
       child: new ConstrainedBox(
       constraints: new BoxConstraints.expand(), 
      ), 
      ), 
     ) 
     ], 
    ), 
    ); 
    } 

    _updateOffset(PointerEvent event) { 
    RenderBox referenceBox = _paintKey.currentContext.findRenderObject(); 
    Offset offset = referenceBox.globalToLocal(event.position); 
    setState(() { 
     _offset = offset; 
    }); 
    } 
} 

class ExpensivePainter extends CustomPainter { 
    @override 
    void paint(Canvas canvas, Size size) { 
    print("Doing expensive paint job"); 
    Random rand = new Random(12345); 
    List<Color> colors = [ 
     Colors.red, 
     Colors.blue, 
     Colors.yellow, 
     Colors.green, 
     Colors.white, 
    ]; 
    for (int i = 0; i < 5000; i++) { 
     canvas.drawCircle(
      new Offset(
       rand.nextDouble() * size.width, rand.nextDouble() * size.height), 
      10 + rand.nextDouble() * 20, 
      new Paint() 
      ..color = colors[rand.nextInt(colors.length)].withOpacity(0.2)); 
    } 
    } 

    @override 
    bool shouldRepaint(ExpensivePainter other) => false; 
} 

class MyCustomPainter extends CustomPainter { 
    final Offset _offset; 

    MyCustomPainter(this._offset); 

    @override 
    void paint(Canvas canvas, Size size) { 
    if (_offset == null) return; 
    canvas.drawCircle(_offset, 10.0, new Paint()..color = Colors.black); 
    } 

    @override 
    bool shouldRepaint(MyCustomPainter other) => other._offset != _offset; 
} 
+0

D'ailleurs, pourquoi utilisez-vous 'Listener' pour le Drap/drop? Il y a un widget 'Draggable' à cette fin. – Darky

+0

Merci pour le conseil - Je ne connaissais pas le Draggable, mais ce n'est pas vraiment adapté dans mon cas: Je suis en train d'écrire une application Flutter pour mon webapp collaborative webboard: https://whiteboardfox.com/ – Mark

Répondre

7

C'est une spécificité de flottement. Nous ne sommes pas en réaction, où les «composants» sont repeints seulement quand leur état/accessoires changent. En flutter, chaque fois qu'un widget doit repeindre l'arbre entier le fera aussi.

Habituellement, ce n'est pas un problème et assez rapide. Mais dans certains cas (comme le vôtre), vous ne le voulez pas. Et c'est là qu'un widget assez non documenté mais important apparaît! RepaintBoundary

Il y a un excellent discours sur la façon dont le pipeline de rendu de Flutter fonctionne ici: https://www.youtube.com/watch?v=UUfXWzp0-DU

Mais en bref, considèrent RepaintBoundary comme ce qui dit flutter de diviser l'opération de peinture en différentes parties.

Quoi qu'il en soit, la solution? Enveloppez votre widget Expensive dans un RepaintBoundary. Et soudain, vous obtenez 60 FPS.

 new RepaintBoundary(
     child: new CustomPaint(
      painter: new ExpensivePainter(), 
      isComplex: true, 
      willChange: false, 
     ), 
    ), 

enter image description here

+0

Cela fonctionne - merci! Merci également pour le lien vidéo mis en signet à la section sur les limites repeindre :) – Mark