7
votes

Impossible de prendre une capture d'écran en flutter

package: flutter / src / rendering / proxy_box.dart ': Échec de l'assertion: ligne 2813 pos 12:'! debugNeedsPaint ': n'est pas vrai.

J'essaie de prendre une capture d'écran en flutter, mais je reçois une exception. J'ai visité de nombreux liens mais rien n'a fonctionné.

Future<Uint8List> _capturePng() async {
try {
  print('inside');
  RenderRepaintBoundary boundary =
  _globalKey.currentContext.findRenderObject();
  ui.Image image = await boundary.toImage(pixelRatio: 3.0);
  ByteData byteData =
  await image.toByteData(format: ui.ImageByteFormat.png);
  var pngBytes = byteData.buffer.asUint8List();
  var bs64 = base64Encode(pngBytes);
  print(pngBytes);
  print(bs64);
  setState(() {});
  return pngBytes;
} catch (e) {
  print(e);
}
}

1 commentaires

Où cette fonction est-elle appelée? Cela ne fonctionnera probablement pas s'il est initState dans initState d'un StatefulWidget car le RenderObject vous récupérez dans le contexte de _globalKey n'a pas encore été dessiné / peint.


4 Réponses :


3
votes

Votre code est déjà correct, vous ne devriez rencontrer aucun problème en mode release car à partir de la documentation :

debugNeedsPaint: indique si les informations de peinture de cet objet de rendu sont sales.

Ceci n'est défini qu'en mode débogage. En général, les objets de rendu ne devraient pas avoir besoin de conditionner leur comportement d'exécution selon qu'ils sont sales ou non, car ils ne doivent être marqués comme sales qu'immédiatement avant d'être disposés et peints.

Il est destiné à être utilisé par des tests et des affirmations.

Il est possible (et en fait assez courant) que debugNeedsPaint soit faux et debugNeedsLayout soit vrai. L'objet de rendu sera toujours repeint dans l'image suivante lorsque c'est le cas, car la méthode markNeedsPaint est implicitement appelée par le framework après la disposition d'un objet de rendu, avant la phase de peinture.

Cependant, si vous avez encore besoin d'une solution, vous pouvez essayer ceci:

Future<Uint8List> _capturePng() async {
  try {
    print('inside');
    RenderRepaintBoundary boundary = _globalKey.currentContext.findRenderObject();
    
    // if it needs repaint, we paint it.
    if (boundary.debugNeedsPaint) {
      Timer(Duration(seconds: 1), () => _capturePng());
      return null;
    }
    
    ui.Image image = await boundary.toImage(pixelRatio: 3.0);
    ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
    var pngBytes = byteData.buffer.asUint8List();
    var bs64 = base64Encode(pngBytes);
    print(pngBytes);
    print(bs64);
    setState(() {});
    return pngBytes;
  } catch (e) {
    print(e);
    return null;
  }
}


9 commentaires

Je pense que ce code ne fonctionnera pas car _capturePng renverra null , puis dans une seconde les octets seront imprimés dans la console. Je veux dire if (boundary.debugNeedsPaint) { Timer(Duration(seconds: 1), () => _capturePng()); return null; }


@DenisZakharov Cela n'arrivera pas de cette façon, lancez-vous et faites-le moi savoir.


Votre Timer planifiée de manière asynchrone, le rappel sera exécuté dans une seconde après que _capturePng renvoyé null. Veuillez vérifier ma solution. Je suis sûr que vous comprendrez ce que je veux dire.


J'espère que vous voterez alors pour ma solution)). BTW j'ai vérifié votre code. Cela fonctionne comme je l'ai dit.


@DenisZakharov Désolé mais je ne suis pas en mesure de tester votre code pour le moment mais je peux voir que vous avez utilisé ma logique


await Future.delayed(const Duration(milliseconds: 20)); est une autre logique et ça marche. Les gars du problème de flutter github.com/flutter/flutter/issues/22308 utilisent également Timer mais leur fonction ne renvoie rien. Donc, leur solution fonctionne aussi. Mais pas le vôtre. C'est étrange que vous ne le voyiez pas.


Votre fonction doit retourner Future <Uint8List> mais renvoie null (en débogage). C'est le problème.


@DenisZakharov Je serai heureux de voir si vous pouvez créer un code reproductible avec erreur en utilisant la solution que j'ai fournie. Vous ne savez probablement pas ce que fait return null .


Continuons cette discussion en chat .



9
votes

Vous pouvez trouver un exemple officiel de toImage ici . Mais il semble que cela ne fonctionne pas sans délai entre la pression du bouton et l'appel toImage .

Il y a un problème dans le référentiel officiel: https://github.com/flutter/flutter/issues/22308

La cause de ceci: votre robinet initialise l'animation pour un bouton et RenderObject.markNeedsPaint est appelé de manière récursive, y compris les parents, vous devez donc attendre que debugNeedsPaint soit à nouveau false . toImage fonction toImage simplement une erreur d'assertion dans ce cas:

class _MyHomePageState extends State<MyHomePage> {
  GlobalKey globalKey = GlobalKey();

  Future<Uint8List> _capturePng() async {
    RenderRepaintBoundary boundary = globalKey.currentContext.findRenderObject();

    if (boundary.debugNeedsPaint) {
      print("Waiting for boundary to be painted.");
      await Future.delayed(const Duration(milliseconds: 20));
      return _capturePng();
    }

    var image = await boundary.toImage();
    var byteData = await image.toByteData(format: ImageByteFormat.png);
    return byteData.buffer.asUint8List();
  }

  void _printPngBytes() async {
    var pngBytes = await _capturePng();
    var bs64 = base64Encode(pngBytes);
    print(pngBytes);
    print(bs64);
  }

  @override
  Widget build(BuildContext context) {
    return RepaintBoundary(
      key: globalKey,
      child: Center(
        child: FlatButton(
          color: Color.fromARGB(255, 255, 255, 255),
          child: Text('Capture Png', textDirection: TextDirection.ltr),
          onPressed: _printPngBytes
        ),
      ),
    );
  }
}

https://github.com/flutter/flutter/blob/f0553ba58e6455aa63fafcdca16100b81ff5c3ce/packages/flutter/lib/src/rendering/proxy_box.dart#L2857

  bool get debugNeedsPaint {
    bool result;
    assert(() {
      result = _needsPaint;
      return true;
    }());
    return result;
  }

https://github.com/flutter/flutter/blob/0ca5e71f281cd549f1b5284e339523ad93544c60/packages/flutter/lib/src/rendering/object.dart#L2011

En fait, la fonction d' assert n'est utilisée que dans le développement, donc comme vous pouvez le voir, vous n'aurez pas de problème avec l'erreur de production. Mais je ne sais pas quel genre de problèmes vous pouvez avoir à la place, probablement non).

Le code suivant n'est pas génial mais ça marche:

  Future<ui.Image> toImage({ double pixelRatio = 1.0 }) {
    assert(!debugNeedsPaint);
    final OffsetLayer offsetLayer = layer;
    return offsetLayer.toImage(Offset.zero & size, pixelRatio: pixelRatio);
  }


1 commentaires

RepaintBoundary (clé: globalKey,)



1
votes

J'ai changé la logique de retour de null à la fonction _capturePng () à nouveau.

Reff: @CopsOnRoad

Vérifiez le code ci-dessous

    Future<Uint8List> _capturePng() async {
  try {
    print('inside');
    RenderRepaintBoundary boundary = _globalKey.currentContext.findRenderObject();
    
    // if it needs repaint, we paint it.
    if (boundary.debugNeedsPaint) {

//if debugNeedsPaint return to function again.. and loop again until boundary have paint.
return _capturePng()); 
    }
    
    ui.Image image = await boundary.toImage(pixelRatio: 3.0);
    ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
    var pngBytes = byteData.buffer.asUint8List();
    var bs64 = base64Encode(pngBytes);
    print(pngBytes);
    print(bs64);
    setState(() {});
    return pngBytes;
  } catch (e) {
    print(e);
    return null;
  }
}


0 commentaires

0
votes

J'ai fait face au même problème

Après une longue période de recherche

Je trouve enfin le problème

si le widget que vous voulez capturer est dans une ListView et que la liste est très longue, donc votre widget est hors écran, la capture échouera.


1 commentaires

je n'utilise Listview nulle part