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); } }
4 Réponses :
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; } }
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 .
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 ), ), ); } }
bool get debugNeedsPaint { bool result; assert(() { result = _needsPaint; return true; }()); return result; }
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); }
RepaintBoundary (clé: globalKey,)
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; } }
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.
je n'utilise Listview nulle part
Où cette fonction est-elle appelée? Cela ne fonctionnera probablement pas s'il est
initState
dansinitState
d'unStatefulWidget
car leRenderObject
vous récupérez dans le contexte de_globalKey
n'a pas encore été dessiné / peint.