【问题标题】:Build the widget off screen在屏幕外构建小部件
【发布时间】:2019-08-08 08:29:20
【问题描述】:

我需要构建一个小部件来获取它的位图。我不在乎小部件出现在屏幕上。

所以我的问题是:我可以在不使用屏幕视图层次结构的情况下以某种方式“在侧面”构建小部件吗?

我没有找到办法。因此,如果这不可能,我可以在屏幕上构建小部件,但实际上不显示它。 我试过Visibility 但这会使RenderObject 为空。使用 Offstage 在断言上调用 toImage() 时会失败:Failed assertion: line 2752 pos 12: ‘!debugNeedsPaint’: is not true.

【问题讨论】:

    标签: flutter


    【解决方案1】:

    编辑:看起来这在最新版本的 Flutter 中出现了问题。不知道为什么,但我猜当 Flutter 确定覆盖层根本不可见时,它现在会避免绘制。这个方法还是可以用的,但是需要结合 translate 才能移出屏幕:https://gist.github.com/itsJoKr/ce5ec57bd6dedf74d1737c1f39481913

    有人推荐使用OverlayEntry,它看起来是最好的解决方案。

    您可以将OverlayEntry 放在当前屏幕下方,使其不可见,而maintainState: true 将被构建。 一个很大的优势是它更容易实现,因为它不会与当前的小部件树混合。

    OverlayState overlayState = Overlay.of(context);
    OverlayEntry entry = OverlayEntry(builder: (context) {
      return RepaintBoundary(key: key, child: yourWidget,); // Using RepaintBoundary to get RenderObject and convert to image
    }, maintainState: true);
    overlayState.insert(entry);
    // doesn't work anymore
    // overlayState.rearrange([entry], above: entry); // Didn't find how to insert it at the bottom of current overlays, so this should rearrange it so that our entry is at the bottom
    

    【讨论】:

    • 执行此操作时,我得到 Unhandled Exception: 'package:flutter/src/rendering/proxy_box.dart': Failed assertion: line 2904 pos 12: '!debugNeedsPaint': is not true。任何clou如何解决这个问题?
    【解决方案2】:

    这应该可以完成工作。我们创建了一个屏幕大小的区域。但是(在屏幕外),它仍然可以作为树的一部分被捕获。

    也可以在这里找到:https://gist.github.com/slightfoot/8eeadd8028c373df87f3a47bd4a35e36

    import 'dart:async';
    import 'dart:typed_data';
    import 'dart:ui' as ui;
    
    import 'package:flutter/material.dart';
    import 'package:flutter/rendering.dart';
    
    void main() {
      runApp(
        MaterialApp(
          theme: ThemeData(
            primarySwatch: Colors.indigo,
            accentColor: Colors.pinkAccent,
          ),
          home: ExampleScreen(),
        ),
      );
    }
    
    class ExampleScreen extends StatefulWidget {
      @override
      _ExampleScreenState createState() => new _ExampleScreenState();
    }
    
    class _ExampleScreenState extends State<ExampleScreen> {
      final _captureKey = GlobalKey<CaptureWidgetState>();
      Future<CaptureResult> _image;
    
      void _onCapturePressed() {
        setState(() {
          _image = _captureKey.currentState.captureImage();
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return CaptureWidget(
          key: _captureKey,
          capture: Material(
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: <Widget>[
                  Text(
                    'These widgets are not visible on the screen yet can still be captured by a RepaintBoundary.',
                  ),
                  SizedBox(height: 12.0),
                  Container(
                    width: 25.0,
                    height: 25.0,
                    color: Colors.red,
                  ),
                ],
              ),
            ),
          ),
          child: Scaffold(
            appBar: AppBar(
              title: Text('Widget To Image Demo'),
            ),
            body: FutureBuilder<CaptureResult>(
              future: _image,
              builder: (BuildContext context, AsyncSnapshot<CaptureResult> snapshot) {
                return SingleChildScrollView(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      Center(
                        child: RaisedButton(
                          child: Text('Capture Image'),
                          onPressed: _onCapturePressed,
                        ),
                      ),
                      if (snapshot.connectionState == ConnectionState.waiting)
                        Center(
                          child: CircularProgressIndicator(),
                        )
                      else if (snapshot.hasData) ...[
                        Text(
                          '${snapshot.data.width} x ${snapshot.data.height}',
                          textAlign: TextAlign.center,
                        ),
                        Container(
                          margin: const EdgeInsets.all(12.0),
                          decoration: BoxDecoration(
                            border: Border.all(color: Colors.grey.shade300, width: 2.0),
                          ),
                          child: Image.memory(
                            snapshot.data.data,
                            scale: MediaQuery.of(context).devicePixelRatio,
                          ),
                        ),
                      ],
                    ],
                  ),
                );
              },
            ),
          ),
        );
      }
    }
    
    class CaptureWidget extends StatefulWidget {
      final Widget child;
      final Widget capture;
    
      const CaptureWidget({
        Key key,
        this.capture,
        this.child,
      }) : super(key: key);
    
      @override
      CaptureWidgetState createState() => CaptureWidgetState();
    }
    
    class CaptureWidgetState extends State<CaptureWidget> {
      final _boundaryKey = GlobalKey();
    
      Future<CaptureResult> captureImage() async {
        final pixelRatio = MediaQuery.of(context).devicePixelRatio;
        final boundary = _boundaryKey.currentContext.findRenderObject() as RenderRepaintBoundary;
        final image = await boundary.toImage(pixelRatio: pixelRatio);
        final data = await image.toByteData(format: ui.ImageByteFormat.png);
        return CaptureResult(data.buffer.asUint8List(), image.width, image.height);
      }
    
      @override
      Widget build(BuildContext context) {
        return LayoutBuilder(
          builder: (BuildContext context, BoxConstraints constraints) {
            final height = constraints.maxHeight * 2;
            return OverflowBox(
              alignment: Alignment.topLeft,
              minHeight: height,
              maxHeight: height,
              child: Column(
                children: <Widget>[
                  Expanded(
                    child: widget.child,
                  ),
                  Expanded(
                    child: Center(
                      child: RepaintBoundary(
                        key: _boundaryKey,
                        child: widget.capture,
                      ),
                    ),
                  ),
                ],
              ),
            );
          },
        );
      }
    }
    
    class CaptureResult {
      final Uint8List data;
      final int width;
      final int height;
    
      const CaptureResult(this.data, this.width, this.height);
    }
    

    【讨论】:

      【解决方案3】:

      在任何地方调用captureImage() 方法之前,请执行此操作

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-05-07
        • 2022-09-27
        相关资源
        最近更新 更多