【问题标题】:Unable to take screenshot in flutter无法在颤动中截取屏幕截图
【发布时间】:2019-12-29 21:14:57
【问题描述】:

package:flutter/src/rendering/proxy_box.dart': 断言失败: line 2813 pos 12: '!debugNeedsPaint': is not true.

我试图在颤动中截取屏幕截图,但我遇到了异常。我访问了许多链接,但没有任何效果。

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

【问题讨论】:

  • 这个函数在哪里调用?如果在 StatefulWidgetinitState 内调用它很可能不起作用,因为您从 _globalKey 的上下文中检索的 RenderObject 尚未绘制/绘制。

标签: flutter


【解决方案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;
  }
}

【讨论】:

  • 我认为这段代码行不通,因为_capturePng 将返回null,然后在一秒钟内将在控制台中打印字节。我的意思是if (boundary.debugNeedsPaint) { Timer(Duration(seconds: 1), () =&gt; _capturePng()); return null; }
  • @DenisZakharov 不会这样的,试一试,告诉我。
  • 你的Timer异步调度,回调会在_capturePng返回null后一秒执行。请检查我的解决方案,我相信你会明白我的意思。
  • 我希望你会投票支持我的解决方案))。顺便说一句,我检查了您的代码。正如我所说的那样工作。
  • @DenisZakharov 抱歉,我目前无法测试您的代码,但我可以看到您使用了我的逻辑
【解决方案2】:

您可以找到官方toImage 示例here。但看起来它在按钮点击和toImage 调用之间没有延迟。

官方仓库有问题:https://github.com/flutter/flutter/issues/22308

原因:您的点击初始化按钮的动画,RenderObject.markNeedsPaint 被包括父母在内的递归调用,所以您应该等待debugNeedsPaint 将再次成为falsetoImage 函数在这种情况下只会抛出断言错误:

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

【讨论】:

  • 重绘边界(key: globalKey,)
【解决方案3】:

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

参考:@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;
  }
}

【讨论】:

    【解决方案4】:

    我也遇到了同样的问题

    经过长时间的研究

    我终于找到问题了

    如果你要捕捉的widget在ListView中,而listview很长,所以你的widget不在屏幕上,捕捉会失败。

    【讨论】:

    • 我没有在任何地方使用 Listview
    • 我正在使用堆栈
    【解决方案5】:

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

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

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

    【讨论】:

      【解决方案6】:

      这里是 Flutter 2.0+ 方法截图并分享到社交媒体的解决方案。

      import 'package:flutter/material.dart';
      import 'package:font_awesome_flutter/font_awesome_flutter.dart';
      import 'package:flutter_datetime_picker/flutter_datetime_picker.dart';
      
      class selClass extends StatelessWidget {
        @override
        Widget build(BuildContext context) {
          return MaterialApp(
            home: sel(),
          );
        }
      }
      
      class sel extends StatefulWidget {
        @override
        _selState createState() => _selState();
      }
      
      class _selState extends State<sel> {
        String _date = "Not set";
      
        @override
        Widget build(BuildContext context) {
          return Scaffold(
            body: SafeArea(child:SingleChildScrollView(
          child: Column(
          children: <Widget>[
          Container(
          color: Colors.grey,
            width: MediaQuery
                .of(context)
                .size
                .width,
            height: MediaQuery
                .of(context)
                .size
                .height / 10,
            child: Center(child: Text("Attendance",
              style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20.0),),),
          ),
          SizedBox(height: 30.0,),
          Padding(
          padding: const EdgeInsets.all(16.0),
          child: Container(
          child: Column(
          mainAxisSize: MainAxisSize.max,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
          RaisedButton(
          shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(5.0)),
          elevation: 4.0,
          onPressed: () {
          DatePicker.showDatePicker(context,
          theme: DatePickerTheme(
          containerHeight: 210.0,
          ),
          showTitleActions: true,
          minTime: DateTime(2000, 1, 1),
          maxTime: DateTime(2022, 12, 31),
          onConfirm: (date) {
          print('confirm $date');
          _date = '${date.year} - ${date.month} - ${date.day}';
          setState(() {});
          },
          currentTime: DateTime.now(),
          locale: LocaleType.en);
          },
          child: Container(
          alignment: Alignment.center,
          height: 50.0,
          child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: <Widget>[
          Row(
          children: <Widget>[
          Container(
          child: Row(
          children: <Widget>[
          Icon(
          Icons.date_range,
          size: 18.0,
          color: Colors.teal,
          ),
          Text(
          " $_date",
          style: TextStyle(
          color: Colors.teal,
          fontWeight: FontWeight.bold,
          fontSize: 18.0),
          ),
          ],
          ),
          )
          ],
          ),
          Text(
          "  Change",
          style: TextStyle(
          color: Colors.teal,
          fontWeight: FontWeight.bold,
          fontSize: 18.0),
          ),
          ],
          ),
          ),
          color: Colors.white,
          ),
          SizedBox(
          height: 20.0,
          ),
          ]
          ,
          )
          ,
          )
          )
          ]))));
        }
      }
      

      【讨论】:

        猜你喜欢
        • 2020-06-23
        • 2019-05-07
        • 2021-12-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-11-12
        • 2014-09-26
        相关资源
        最近更新 更多