无法拍打屏幕截图

时间:2019-08-25 09:59:17

标签: flutter

  

package:flutter / src / rendering / proxy_box.dart':失败的断言:2813行pos 12:'!debugNeedsPaint':不正确。

我正在尝试拍打屏幕截图,但出现异常。我访问了许多链接,但没有任何效果。

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);
}
}

5 个答案:

答案 0 :(得分:1)

您的代码已经很好,在发布模式下您应该不会遇到任何问题,因为来自docs

  

debugNeedsPaint:此渲染对象的绘画信息是否肮脏。

     

仅在调试模式下设置。通常,渲染对象不必根据它们是否变脏来限制其运行时行为,因为仅应在布局和绘制之前将它们标记为肮脏。

     

它打算由测试和断言使用。

     

debugNeedsPaint可能为false,debugNeedsLayout为true,这是可能的(并且确实很常见)。在这种情况下,渲染对象仍将在下一帧中重新绘制,因为在绘制对象之前,在绘制阶段之前,markNeedsPaint方法由框架隐式调用。

但是,如果您仍然需要解决方案,可以尝试以下操作:

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;
  }
}

答案 1 :(得分:1)

您可以找到官方toImage示例here。但是看来在按钮点击和toImage调用之间没有延迟就无法正常工作。

官方存储库中存在一个问题:https://github.com/flutter/flutter/issues/22308

原因在于:您的点击会初始化按钮的动画,并且RenderObject.markNeedsPaint将被递归调用(包括父母),因此您应该等待debugNeedsPaint再次成为false时。 toImage函数在这种情况下只会引发断言错误:

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

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

实际上,assert函数仅在开发中使用,因此您可以看到生产错误不会带来麻烦。但是我不知道您会遇到什么样的麻烦,也许没有。)

下一个代码不是很好,但是可以工作:

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
        ),
      ),
    );
  }
}

答案 2 :(得分:1)

我将返回的逻辑从null再次更改为_capturePng()函数。

Reff:@CopsOnRoad

检查以下代码

    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;
  }
}

答案 3 :(得分:0)

我遇到了同样的问题

经过长时间的研究

我终于找到问题了

如果要捕获的小部件位于ListView中,并且列表视图很长,则您的小部件不在屏幕上,捕获将失败。

答案 4 :(得分:0)

这是对已接受答案的重要补充。根据 debugNeedsPaint 的文档,在发布模式下检查此标志会使应用程序崩溃 (In release builds, this throws.)

所以我们只需要在调试模式下检查这个标志,使用 kDebugMode 中的 import 'package:flutter/foundation.dart';

    var debugNeedsPaint = false;

    //https://stackoverflow.com/questions/49707028/how-to-check-flutter-application-is-running-in-debug
    //In release builds, this throws (boundary.debugNeedsPaint)
    if (kDebugMode) debugNeedsPaint = boundary.debugNeedsPaint;

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