Flutter 小吃店替代方案还是比将所有内容包装在 Scaffold 中更简单的方法?

Posted

技术标签:

【中文标题】Flutter 小吃店替代方案还是比将所有内容包装在 Scaffold 中更简单的方法?【英文标题】:Flutter snackbar alternative or easier method than wrapping everything in Scaffold? 【发布时间】:2019-08-28 12:07:06 【问题描述】:

我正在开发我的第一个 Flutter 应用(在我的 android 手机上进行调试)。我有一个包含行项目的列表。当您长按该行时,它会将内容复制到用户的剪贴板中。这很好用!

但我需要让用户知道内容已被复制。

我尝试按照许多教程来尝试将行包围在构建方法中或在 Scaffold 中,但我无法工作。是否有另一种方法来通知用户(简单地)类似“已复制!”发生了吗?

请注意下面注释掉的Scaffold.of(...。似乎必须有一种更简单的方法来通知用户,而不是将所有内容包装在 Scaffold 中。 (当我尝试时,它会破坏我的布局)。

import 'package:flutter/material.dart';
import 'package:my_app/Theme.dart' as MyTheme;
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/services.dart';

class RowRule extends StatelessWidget 
  final DocumentSnapshot ruleGroup;

  RowRule(this.ruleGroup);

  _buildChildren() 
    var builder = <Widget>[];
    if (!ruleGroup['label'].isEmpty) 
      builder.add(new Text(ruleGroup['label'],
          style: MyTheme.TextStyles.articleContentLabelTextStyle));
    
    if (!ruleGroup['details'].isEmpty) 
      builder.add(new Text(ruleGroup['details'],
          style: MyTheme.TextStyles.articleContentTextStyle));
    
    return builder;
  


  @override
  Widget build(BuildContext context) 
    return new GestureDetector(
        onLongPress: () 
          Clipboard.setData(new ClipboardData(text: ruleGroup['label'] + " "  + ruleGroup['details']));
//          Scaffold.of(context).showSnackBar(SnackBar
//            (content: Text('text copied')));
        ,
        child: Container(
          margin: const EdgeInsets.symmetric(vertical: 3.0),
          child: new FlatButton(
            color: Colors.white,
            padding: EdgeInsets.symmetric(horizontal: 0.0),
            child: new Stack(
              children: <Widget>[
                new Container(
                  margin: const EdgeInsets.symmetric(
                    vertical: MyTheme.Dimens.ruleGroupListRowMarginVertical),
                  child: new Container(
                      child: Padding(
                        padding: EdgeInsets.symmetric(horizontal: 32.0, vertical: 8.0),
                        child: new Column(
                          crossAxisAlignment: CrossAxisAlignment.stretch,
                          children: _buildChildren(),
                        ),
                    )),
              )
            ],
          ),
        ),
      ));
    
  

目标是有一个像这样的页面(见图),我有它,它可以工作和滚动......等等,但我不能让它与脚手架一起工作,因此,一直没能使用小吃店。每个“行”(此文件用于)都应在 longPress 上显示一个快餐栏。

【问题讨论】:

【参考方案1】:

您可以使用GlobalKey 使其按您想要的方式工作。

由于我无权访问您的数据库内容,这就是我给您的想法的方式。将此代码复制并粘贴到您的课程中,并进行相应的更改。我也认为您的RowRule 课程有问题,您可以复制我给您的完整代码并运行吗?

void main() => runApp(MaterialApp(home: HomePage()));

class HomePage extends StatelessWidget 
  final GlobalKey<ScaffoldState> _key = GlobalKey();

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      backgroundColor: Color(0xFFFFFFFF).withOpacity(0.9),
      key: _key,
      body: Column(
        children: <Widget>[
          Container(
            color: Color.fromRGBO(52, 56, 245, 1),
            height: 150,
            alignment: Alignment.center,
            child: Container(width: 56, padding: EdgeInsets.only(top: 12), decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.yellow)),
          ),
          Expanded(
            child: ListView.builder(
              padding: EdgeInsets.zero,
              itemCount: 120,
              itemBuilder: (context, index) 
                return Container(
                  color: Colors.white,
                  margin: const EdgeInsets.all(4),
                  child: ListTile(
                    title: Text("Row #$index"),
                    onLongPress: () => _key.currentState
                      ..removeCurrentSnackBar()
                      ..showSnackBar(SnackBar(content: Text("Copied \"Row #$index\""))),
                  ),
                );
              ,
            ),
          ),
        ],
      ),
    );
  

【讨论】:

我在这个文件/布局中没有脚手架。我认为这是一种建议的方法,但是如何让 GlobalKey 在这里可以访问?我见过的所有例子都是这样的,其中 Scaffold 与 longPress 在同一个文件中。 @Dave 您可以轻松地将您的小部件包装在Scaffold 中,在您的示例中将GestureDetector 设为Scaffold 的子级。 当我用 Scaffold 包裹 GestureDetector 并将 GestureDetector 设为“body”时,我得到:Another exception was thrown: RenderCustomMultiChildLayoutBox object was given an infinite size during layout.“_RenderInkFeatures”、“RenderPhysicalModel”、“RenderRepaintBoundary”和“同样的错误”渲染索引语义”。【参考方案2】:

这是一个名为“Flushbar”的 Snackbar 的简单插件替代品。 你可以在这里获取插件 - https://pub.dartlang.org/packages/flushbar 您不必将任何小部件包装到脚手架中,您也可以获得很多修改,例如背景渐变,在 Snackbar 中添加表单等。 在 GestureDetectore 中的 onLongPressed 中,您可以执行此操作。

onLongPressed:()
Clipboard.setData(new ClipboardData(text: ruleGroup['label'] + " "  + ruleGroup['details']));
Flushbar(
             message:  "Copied !!",
             duration:  Duration(seconds: 3),              
          )..show(context);

这将在您的应用程序中显示您希望看到的小吃栏,您还可以获得很多可用的修改,以便您可以根据您的应用程序使其看起来。

【讨论】:

剪贴板有什么用?这甚至与您的答案有关吗? 仅供参考:Flushbar 已停产。 pub.dartlang.org/packages/flushbar【参考方案3】:

您需要做几件事,例如使用FlatButtononPressed 属性必须允许点击,将GestureDetector 包装在Scaffold 中。我进一步修改了代码,以便它使用GlobalKey 让您更轻松。

这是最终代码(你的方式

class RowRule extends StatelessWidget 
  final GlobalKey<ScaffoldState> globalKey = GlobalKey();
  final DocumentSnapshot ruleGroup;

  RowRule(this.ruleGroup);

  _buildChildren() 
    var builder = <Widget>[];
    if (!ruleGroup['label'].isEmpty) 
      builder.add(new Text(ruleGroup['label'], style: MyTheme.TextStyles.articleContentLabelTextStyle));
    
    if (!ruleGroup['details'].isEmpty) 
      builder.add(new Text(ruleGroup['details'], style: MyTheme.TextStyles.articleContentTextStyle));
    
    return builder;
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      key: globalKey,
      body: GestureDetector(
        onLongPress: () 
          Clipboard.setData(new ClipboardData(text: ruleGroup['label'] + " " + ruleGroup['details']));
          globalKey.currentState
            ..removeCurrentSnackBar()
            ..showSnackBar(SnackBar(content: Text('text copied')));
        ,
        child: Container(
          margin: const EdgeInsets.symmetric(vertical: 3.0),
          child: new FlatButton(
            onPressed: () => print("Handle button press here"),
            color: Colors.white,
            padding: EdgeInsets.symmetric(horizontal: 0.0),
            child: new Stack(
              children: <Widget>[
                new Container(
                  margin: const EdgeInsets.symmetric(vertical: MyTheme.Dimens.ruleGroupListRowMarginVertical),
                  child: new Container(
                    child: Padding(
                      padding: EdgeInsets.symmetric(horizontal: 32.0, vertical: 8.0),
                      child: new Column(
                        crossAxisAlignment: CrossAxisAlignment.stretch,
                        children: _buildChildren(),
                      ),
                    ),
                  ),
                )
              ],
            ),
          ),
        ),
      ),
    );
  

【讨论】:

这会引发以下错误:RenderCustomMultiChildLayoutBox object was given an infinite size during layout.【参考方案4】:

我在 pub 上制作了一个下拉横幅 package,让您可以轻松地通知用户错误或确认成功。随着我继续添加视觉上丰富的功能,这项工作正在进行中。

【讨论】:

【参考方案5】:

我不确定您的 build() 方法是否已完成或者您尚未更改它,因为它包含许多多余的小部件。就像没有必要在ContainerPadding 中添加Container 以及FlatButton,这将使整个屏幕可点击。同时拥有Column 也不是一个好主意,因为如果您有更多数据,您的屏幕可能会溢出。请改用ListView

因此,如果您接受我的建议,请使用这个简单的代码,它应该可以为您提供您真正想要的东西。 (见build()方法只有5行。

class RowRule extends StatelessWidget 
  final GlobalKey<ScaffoldState> globalKey = GlobalKey();

  final DocumentSnapshot ruleGroup;

  RowRule(this.ruleGroup);

  _buildChildren() 
    var builder = <Widget>[];
    if (!ruleGroup['label'].isEmpty) 
      builder.add(
        ListTile(
          title: Text(ruleGroup['label'], style: MyTheme.TextStyles.articleContentLabelTextStyle),
          onLongPress: () 
            globalKey.currentState
              ..removeCurrentSnackBar()
              ..showSnackBar(SnackBar(content: Text("Clicked")));
          ,
        ),
      );
    
    if (!ruleGroup['details'].isEmpty) 
      builder.add(
        ListTile(
          title: Text(ruleGroup['details'], style: MyTheme.TextStyles.articleContentTextStyle),
          onLongPress: () 
            globalKey.currentState
              ..removeCurrentSnackBar()
              ..showSnackBar(SnackBar(content: Text("Clicked")));
          ,
        ),
      );
    
    return builder;
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      key: globalKey,
      body: ListView(children: _buildChildren()),
    );
  

【讨论】:

我试过这个,我得到了很多错误,其中一些与我尝试使用脚手架时遇到的相同:“抛出另一个异常:_RenderInkFeatures 对象在布局期间被赋予了无限大小。”和其他一些人。 你能分享一下你使用RowRule的类吗?【参考方案6】:

我阅读了您的所有答案,这是我的结论:

您需要位于树中小部件上方的ScaffoldState 对象才能显示Snackbar。您可以按照许多人的建议通过GlobalKey 获得它。如果Scaffold 是在小部件的build 内创建的,则相当简单,但如果它在小部件之外(在您的情况下),那么它就会变得复杂。您需要通过子小部件的构造函数参数传递该密钥,无论您需要它。

Scaffold.of(context) 是一种非常巧妙的方法。就像InheritedWidget 一样,Scaffold.of(BuildContext context) 让您可以访问树上方最近的ScaffoldState 对象。否则,如果您的树非常深,那么获取该实例(通过将其作为构造函数参数传递)可能是一场噩梦。

抱歉,让您失望了,但我认为没有比这更好或更清洁的方法了,如果您想获得不是在该小部件的构建内部构建的ScaffoldState。您可以在任何以Scaffold 为父级的小部件中调用它。

【讨论】:

Scaffold.of(context) 与 InheritedWidgets 无关 @RémiRousselet 我做了一些搜索,你说得对,Scafool​​d 不是 IW,但它确实提供了类似的 .of() 静态方法来获取最近的 State 对象。我只是假设它会是IW。无论如何纠正了我的答案。谢谢你启发我!!

以上是关于Flutter 小吃店替代方案还是比将所有内容包装在 Scaffold 中更简单的方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Flutter 上持续检查互联网连接与否?

如何在颤动中更改小吃店的填充

在 SnackBar 上包装内容高度

如何在 Flutter 中的 navigator.pop(context) 之后显示小吃栏?

Flutter SnackBar 的高度

Flutter Redux 小吃店