【问题标题】:Is there a way to make a composited widget inherit the properties of the widgets it is composed from in flutter?有没有办法让复合小部件继承它在颤动中组成的小部件的属性?
【发布时间】:2022-08-19 22:31:49
【问题描述】:

我创建了一个小部件,根据其FocusNode 的焦点,它要么变为TextField,要么变为Text。它工作得很好,here is the code(这里没有把它包括在内)。

问题是,TextTextField 有很多参数来设置它们的样式,我发现将所有这些参数复制到我的新混合小部件的构造函数中只是为了将它们传递给新的这两个小部件并不是最佳选择build 方法而不对它们做任何其他事情。

例如TextField 在其构造函数中有超过 50 个参数,这是将它与另一个小部件组合并仍然获得所有这些选项以设置 TextField 样式的唯一方法,是将这些参数中的每一个复制到我的构造函数中,然后除了将它们传递给TextField 之外,没有对它们做任何事情?

那么是否有一些设计模式或一些解决方案可以让这两个小部件的参数在新小部件的构造函数中可用?

注意:请参阅comment of M. Azyoksul on Gunter\'s comment here 以获取更多上下文。

问题的最小示例:

// this widget is from external library (not under my control)
class WidgetA extends StatelessWidget {
  // very long list of fields
     A1 a1;
     
     A2 a2;
     
     ... (long list of fields)

   // constructor
   WidgetA(this.a1, this.a2, ...);
  
}

// this widget is from external library
class WidgetB extends StatelessWidget {
  // very long list of fields
     B1 b1;
     
     B2 b2;
     
     ... (long list of fields)

   // constructor
   WidgetB(this.b1, this.b2, ...);
  
}


// now this is the widget I want to create
class HybridWidget extends StatelessWidget {

     // time consuming: I copy all the fields of WidgetA and 
     // WidgetB into the new constructor just to pass them as they are without doing anything else useful on them
     A1 a1;
     A2 a2;
     ...
     

     B1 b1;
     B2 b2;
     ...

 // the new constructor: (not optimal at all)
 HybridWidget(this.a1,this.a2,...,this.b1,this.b2,...);

  @override
  Widget build(BuildContext context) {
    // for example:
    if(some condition)
     return Container(child:WidgetA(a1,a2, ...),...); <--- here is the problem, I am not doing anything other than passing the \"styling\" parameters as they were passed to me, alot of copy/paste
    if(other condition)
      return Container(Widget2(b1,b2, ... ),...); <--- and here is the same problem
    
    //... other code
  }
}

    标签: android flutter dart design-patterns


    【解决方案1】:

    我不是android 家伙,但是,在我看来,这些原则可以在这里应用:

    1. 为参数创建类

    2. 使用singleton pattern 在组件之间共享数据

    3. 如果你想通知其他用户数据的变化,那么你可以使用observer pattern

      让我澄清一下我的意思。

      1.为参数制作类

      您可以为参数创建类:

      public class MyParams
      { 
          public int Param_1 { get; set; }
      
          public int Param_2 { get; set; }
      
          public int Param_3 { get; set; }
      }
      

      并使用它们:

      class Widget1 extends StatelessWidget {
        // very long parameter list
         Widget1(MyParams params)
        
      }
      

      2. 使用单例模式在组件之间共享数据

      让我通过 C# 展示单例模式:

      public sealed class MyParams
      {
          public int Param_1 { get; set; }
      
          public int Param_2 { get; set; }
      
          public int Param_3 { get; set; }
      
          //the volatile keyword ensures that the instantiation is complete 
          //before it can be accessed further helping with thread safety.
          private static volatile MyParams _instance;
          private static readonly object SyncLock = new();
      
          private MyParams()  {}
      
          //uses a pattern known as double check locking
          public static MyParams Instance
          {
              get
              {
                  if (_instance != null)
                  {
                      return _instance;
                  }
                  lock (SyncLock)
                  {
                      if (_instance == null)
                      {
                          _instance = new MyParams();
                      }
                  }
                  return _instance;
              }
          } 
      }
      

      然后你可以在你的小部件中使用它:

      class HybridWidget extends StatelessWidget {
        
        // how to get parameters of widget1 and widget2 here?
        // is there a way other than copying all of them?
        public void GetParams()
        {
            var params = MyParams.Instance;
        }
        
        // ... other code is omitted for the brevity      
       
      }
      

      React 中的 Redux 工具广泛使用这些模式,例如 Singleton 和 Observer。 Read more in this great article

      更新

      如果你有很多小部件,那么你可以在参数集合WidgetParams 中添加所有参数。 然后您可以从任何其他小部件访问这些参数。

      public sealed class MyParams
      {
          //the volatile keyword ensures that the instantiation is complete 
          //before it can be accessed further helping with thread safety.
          private static volatile MyParams _instance;
          private static readonly object SyncLock = new();
      
          private MyParams()
          {
          }
      
          //uses a pattern known as double check locking
          public static MyParams Instance
          {
              get
              {
                  if (_instance != null)
                  {
                      return _instance;
                  }
                  lock (SyncLock)
                  {
                      if (_instance == null)
                      {
                          _instance = new MyParams();
                      }
                  }
                  return _instance;
              }
          }
      
          List<WidgetParams> WidgetParams = new List<WidgetParams>();
      }
      
      public class WidgetParams
      {
          /// <summary>
          /// Widget name
          /// </summary>
          public int Name { get; set; }
      
          public object Params { get; set; }
      }
      

      您可以从任何其他小部件访问这些参数:

      var params = MyParams.Instance.WidgetParams;
      

      因此,您的组件中的代码如下所示:

      class HybridWidget extends StatelessWidget {
        
        // how to get parameters of widget1 and widget2 here?
        // is there a way other than copying all of them?
        public void GetParams()
        {
            var params = MyParams.Instance.WidgetParams;
        }
        
        // ... other code is omitted for the brevity      
       
      }
      

    【讨论】:

    • 当你说 You can create class for parameters: ...and use them: ... 时,很遗憾,Widget1Widget2 不在我的控制之下,它们是 Flutter SDK 附带的 material 库的一部分。因此,如果我要按照您的建议进行操作,我将不得不将所有参数复制到此类 MyParams 中,然后我可以在多个新小部件中使用它,但我正在寻找一种不复制它们的方法第一名。至于您提到的其他模式(2 和 3),我知道它们是什么,但没有完全理解您的意思,我应该在这里使用它们。谢谢。
    • 因为如果我将参数复制到这个新类MyParams 中,那么每次我需要创建一个由多个其他小部件组成的小部件(我们确实经常这样做)然后我需要每次复制每个小部件进入一个新类ThatWidgetParams,然后我可以在新的组合小部件中使用它
    • 请参阅已编辑的问题以获取更多说明
    • @Haidar 请看我更新的答案
    • @haidar 也许。好的,我们等待其他回复
    【解决方案2】:

    我处理它的方式是将TextTextfield 作为参数传递,而不是您要求的所有参数。

    const DoubleStateText({
        Key? key,
        required this.initialText,
        required this.textFieldWidget,
        required this.textWidget,
        this.onGainFocus,
        this.onLoseFocus,
        this.padding = EdgeInsets.zero,
      }) : super(key: key);
    

    这样我会保持简单:DoubleStateText 的作用只是对小部件进行智能切换。

    【讨论】:

    • 但是我怎样才能在TextField 上使用TextEditingControllerFocusNode,(你知道小部件是不可变的)?如果您的意思是我应该在DoubleStateTextbuild 中再次创建TextTextField,那么我们又回到了同样的问题,但现在它不是在构造函数的级别上,而是在build 方法。但是,如果我只是按照您所说的那样进行“切换”,那么这当然比我的方法更好,但不幸的是,我不是在这里
    【解决方案3】:

    构建器模式可能会起作用(不确定这是否是正确的术语)。

    首先定义我们的函数签名:

    typedef TextBuilder = Widget Function(String text);
    typedef TextFieldBuilder = Widget Function(TextEditingController, FocusNode);
    

    这些将用于您的DoubleStatetext...

    DoubleStateText(
        initialText: 'Initial Text',
        textBuilder: (text) => Text(text, style: TextStyle(fontSize: 18)),
        textFieldBuilder: (controller, focusNode) =>
            TextField(controller: controller, focusNode: focusNode, cursorColor: Colors.green,)
    ),
    

    ...所以不是将所有参数传递给DoubleStateText,而是传递给它的构建器(函数)将TextTextField 包含我们想要的所有参数。然后DoubleStateText 只是调用构建器而不是创建Text/TextField 本身。

    DoubleStateText 的更改:

    class DoubleStateText extends StatefulWidget {
      final String Function()? onGainFocus;
    
      final String? Function(String value)? onLoseFocus;
    
      final String initialText;
    
      // NEW ==================================================
      final TextBuilder textBuilder;
    
      // NEW ==================================================
      final TextFieldBuilder textFieldBuilder;
    
      final ThemeData? theme;
    
      final InputDecoration? inputDecoration;
    
      final int? maxLines;
    
      final Color? cursorColor;
    
      final EdgeInsets padding;
    
      final TextStyle? textStyle;
    
      const DoubleStateText({
        Key? key,
        this.onGainFocus,
        this.onLoseFocus,
        required this.initialText,
        required this.textBuilder, // NEW ==================================================
        required this.textFieldBuilder, // NEW ==================================================
        this.theme,
        this.inputDecoration,
        this.maxLines,
        this.cursorColor,
        this.padding = EdgeInsets.zero,
        this.textStyle,
      }) : super(key: key);
    
      @override
      State<DoubleStateText> createState() => _DoubleStateTextState();
    }
    
    class _DoubleStateTextState extends State<DoubleStateText> {
      bool _isEditing = false;
      late final TextEditingController _textController;
      late final FocusNode _focusNode;
      late final void Function() _onChangeFocus;
    
      @override
      void initState() {
        super.initState();
    
        _textController = TextEditingController(text: widget.initialText);
        _focusNode = FocusNode();
    
        // handle Enter key event when the TextField is focused
        _focusNode.onKeyEvent = (node, event) {
          if (event.logicalKey == LogicalKeyboardKey.enter) {
            setState(() {
              String? text = widget.onLoseFocus?.call(_textController.text);
              _textController.text = text ?? widget.initialText;
              _isEditing = false;
            });
            return KeyEventResult.handled;
          }
          return KeyEventResult.ignored;
        };
    
        // handle TextField lose focus event due to other reasons
        _onChangeFocus = () {
          if (_focusNode.hasFocus) {
            String? text = widget.onGainFocus?.call();
            _textController.text = text ?? widget.initialText;
          }
          if (!_focusNode.hasFocus) {
            setState(() {
              String? text = widget.onLoseFocus?.call(_textController.text);
              _textController.text = text ?? widget.initialText;
              _isEditing = false;
            });
          }
        };
        _focusNode.addListener(_onChangeFocus);
      }
    
      @override
      void dispose() {
        _textController.dispose();
        _focusNode.removeListener(_onChangeFocus);
        _focusNode.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        Widget child;
        if (!_isEditing) {
          child = InkWell(
              onTap: () {
                setState(() {
                  _isEditing = true;
                  _focusNode.requestFocus();
                });
              },
              //child: Text(_textController.text, style: widget.textStyle),
              // NEW: use the builders ==========================================
              child: widget.textBuilder(_textController.text));
        } else {
          // NEW: use the builders ==========================================
          child = widget.textFieldBuilder(_textController, _focusNode);
          /*child = TextField(
            focusNode: _focusNode,
            controller: _textController,
            decoration: widget.inputDecoration,
            maxLines: widget.maxLines,
            cursorColor: widget.cursorColor,
          );*/
        }
    
        child = Padding(
          padding: widget.padding,
          child: child,
        );
    
        child = Theme(
          data: widget.theme ?? Theme.of(context),
          child: child,
        );
    
        return child;
      }
    }
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-05-09
      • 2020-12-13
      • 2020-10-29
      • 2011-04-20
      • 1970-01-01
      • 2019-08-24
      • 1970-01-01
      • 2017-04-27
      相关资源
      最近更新 更多