首页 文章

从State类调用方法

提问于
浏览
-1

给定有状态小部件,以某种方式可以调用State类中定义的方法( extends State<NameOfTheWidget> ) . 实际上,我只想重建_State类,比如调用setState(),但是从类外部调用 . 我知道如何从孩子到父母,但不是反之亦然 .

class Foo extends StatefulWidget{
  State createState() => new _State();
  //...bar() ??
}

class _State extends State<Foo>{
  @override
  Widget build(BuildContext context) {...}

  void bar(){...}
}

EDIT :一些真实的代码

首先,我们认为相当于内部小部件;这是一个自定义的文本字段 . 关键是我想根据boolean _activo变量启用和禁用它 .

import 'package:flutter/material.dart';
import 'package:bukit/widgets/ensure.dart';

class EntradaDatos extends StatelessWidget{
  final String _titulo;
  final String _hint;
  TextEditingController _tec;
  FocusNode _fn = new FocusNode();
  final String Function(String s) _validador;
  final TextInputType _tit;
  bool _activo;

  /*
   *  CONSTRUCTOR
   */
  EntradaDatos(this._titulo, this._hint, this._validador, this._tit, this._activo){
    _tec = new TextEditingController();     
  }

  @override
  Widget build(BuildContext context){
    print('Construyendo');
    return new EnsureVisibleWhenFocused(
      focusNode: _fn,
      child: new TextFormField(
        enabled: _activo,
        keyboardType: _tit,
        validator: _validador,
        autovalidate: true,
        focusNode: _fn,
        controller: _tec,
        decoration: InputDecoration(
          labelText: _titulo,
          hintText: _hint
        ),
      )
    );
  }

  String getContenido(){
    return _tec.text;
  }
}

然后我有一个前面的文本字段的具体实现,它只是扩展它:

import 'package:flutter/material.dart';
import 'package:bukit/widgets/entrada_datos.dart';

class EntradaMail extends EntradaDatos{

  static String _hint = "nombre@dominio.es";
  static String _validador(String s){
    if(s.isEmpty){
      return 'El campo es obligatorio';
    }else{
      if(!s.contains('@') || !s.contains('.') || s.contains(' ')){
        return 'Introduce una dirección válida';
      }else{
        String nombre = s.substring(0, s.indexOf('@'));
        String servidor = s.substring(s.indexOf('@')+1, s.lastIndexOf('.'));
        String dominio = s.substring(s.lastIndexOf('.')+1);
        if(nombre.length < 2 || servidor.length < 2 || dominio.length < 2){
          return 'Introduce una dirección válida';
        }
      }
    }
  }

  EntradaMail(String titulo, bool activo) : super(titulo, _hint, _validador, TextInputType.emailAddress, activo);
}

最后,相当于我的outter小部件 . 它只是一个复选框,后跟prevoius EntradaEmail 小部件 . 据我所知,一旦按下复选框并进行onChange调用,setState调用应重建所有内容,但我与调试消息对比,从未调用第一个内部窗口小部件的构建方法 . 我的观点是根据复选框启用和禁用文本字段 .

class CampoEnvio extends StatefulWidget{

  EntradaMail _mail;
  EntradaMovil _movil;
  String _tituloMail;
  String _tituloMovil;  
  bool _usaMail = false;
  bool _usaMovil = false;

  CampoEnvio(this._tituloMail, this._tituloMovil){
    _mail = new EntradaMail(_tituloMail, _usaMail);
    _movil = new EntradaMovil(_tituloMovil, _usaMovil);
  }

  State createState() => _State(_mail, _movil, _usaMail, _usaMovil, _tituloMail, _tituloMovil);

}

class _State extends State<CampoEnvio>{

  bool _usaMail;
  bool _usaMovil;
  String _tituloMail;
  String _tituloMovil;
  EntradaMail _mail;
  EntradaMovil _movil;

  _State(this._mail, this._movil, this._usaMail, this._usaMovil, this._tituloMail, this._tituloMovil);


  @override
  Widget build(BuildContext context){
    return new Column(
      children: <Widget>[
        new ListTile(
          leading: new SizedBox(
            width: 70.0,
            child: new Row(
              children: <Widget>[
                new Checkbox(
                  value: _usaMail,
                  activeColor: Colors.black,
                  onChanged: (value) {
                    setState(() {
                      _usaMail = value;                 
                    });
                  },
                ),
              ],
            ),
          ),
          title: _mail,
        ),
        //...
        new Divider()
      ],
    );
  }
}

2 回答

  • 1

    好的,既然您已添加示例代码,我将尝试解释您的窗口小部件无法正常工作的原因,并且我将尝试解释可以进行哪些其他改进 .


    首先,您可以通过为所有小部件使用命名构造函数来提高代码的可读性,例如在我的其他答案中(您可以使用Android Studio自动生成它们:定义一些最终字段,然后按灯泡按钮生成构造函数) .


    下一个问题是创建 TextEditingController 的小部件必须始终是有状态的小部件!否则,用户输入的内容将在每次构建后消失!

    通常你会从父窗口小部件传递 TextEditingController (在提交时处理进程数据的窗口小部件)


    此外,不鼓励扩展小部件 . 相反,使用组合物,例如:

    class EntradaMail extends StatelessWidget {
      final String titulo;
      // ...    
      Widget build(BuildContext context) {
        return EntradaDatos(
          titulo: titulo,
          //...
        )
      }
    }
    

    窗口小部件属性应始终是公共的和最终的(永远不要以 _ 开头) .


    你在 CampoEnvio 做了一些奇怪的事情 .

    首先,由于某种原因,您将窗口小部件的所有属性传递给 createState 中的 State . 这有一些你可能不打算的后果 .

    通常情况下, State 类具有构造函数参数,并且通常会将属性窗口小部件中的属性传递给状态 .

    问题是 createState 只被调用一次,当你在父窗口小部件中调用 initState 时,它不再被调用 . 保持状态直到小部件被丢弃 .

    这意味着你的状态构造函数也只被调用一次, _StateCampoEnvio )中的字段将始终保持不变 . 即使重建父级并再次调用 CampoEnvio 的构造函数, _State 中的旧值也不会被替换 .


    您在 StatefulWidget 中创建小部件( EntradaMailEntradaMovil )也非常困难 .

    扩展 StatefulWidget 的类不应该这样做!它基本上只是一个"bag"属性 .


    以下是完整的固定示例代码,遵循上述约定:

    class EntradaDatos extends StatefulWidget {
      EntradaDatos({Key key, this.titulo, this.hint, this.validador, this.tit, this.activo}) : super(key: key);
    
      final String titulo;
      final String hint;
    
      final String Function(String s) validador;
      final TextInputType tit;
      final bool activo;
    
      @override
      State<StatefulWidget> createState() => _EntradaDatosState();
    }
    
    class _EntradaDatosState extends State<EntradaDatos> {
      // FocusNode and TextEditingController must be the same for the whole lifetime of the widget
      // => put into State
      TextEditingController _tec;
      FocusNode _fn;
    
      @override
      void initState() {
        super.initState();
        _tec = new TextEditingController();
        _fn = new FocusNode();
      }
    
      @override
      Widget build(BuildContext context) {
        print('Construyendo');
        return new EnsureVisibleWhenFocused(
            focusNode: _fn,
            child: new TextFormField(
              enabled: widget.activo,
              keyboardType: widget.tit,
              validator: widget.validador,
              autovalidate: true,
              focusNode: _fn,
              controller: _tec,
              decoration: InputDecoration(labelText: widget.titulo, hintText: widget.hint),
            ));
      }
    
      String getContenido() {
        return _tec.text;
      }
    }
    
    class EntradaMail extends StatelessWidget {
      static String _hint = "nombre@dominio.es";
    
      static String _validador(String s) {
        if (s.isEmpty) {
          return 'El campo es obligatorio';
        } else {
          if (!s.contains('@') || !s.contains('.') || s.contains(' ')) {
            return 'Introduce una dirección válida';
          } else {
            String nombre = s.substring(0, s.indexOf('@'));
            String servidor = s.substring(s.indexOf('@') + 1, s.lastIndexOf('.'));
            String dominio = s.substring(s.lastIndexOf('.') + 1);
            if (nombre.length < 2 || servidor.length < 2 || dominio.length < 2) {
              return 'Introduce una dirección válida';
            }
          }
        }
      }
    
      EntradaMail({Key key, this.titulo, this.activo}) : super(key: key);
    
      final String titulo;
      final bool activo;
    
      @override
      Widget build(BuildContext context) {
        // use composition instead of inheritance
        return EntradaDatos(
          titulo: titulo,
          activo: activo,
          validador: _validador,
          hint: _hint,
          tit: TextInputType.emailAddress,
        );
      }
    }
    
    class CampoEnvio extends StatefulWidget {
      const CampoEnvio({Key key, this.tituloMail, this.tituloMovil}) : super(key: key);
    
      final String tituloMail;
      final String tituloMovil;
    
      @override
      State<StatefulWidget> createState() => new _CampoEnvioState();
    }
    
    class _CampoEnvioState extends State<CampoEnvio> {
      // I guess these variables are modified here using setState
      bool _usaMail;
      bool _usaMovil;
    
      @override
      Widget build(BuildContext context) {
        // just rebuild the widgets whenever build is called!
        final mail = new EntradaMail(
          titulo: widget.tituloMail,
          activo: _usaMail,
        );
        final movil = new EntradaMovil(
          titulo: widget.tituloMovil,
          activo: _usaMovil,
        );
    
        return new Column(
          children: <Widget>[
            new ListTile(
              leading: new SizedBox(
                width: 70.0,
                child: new Row(
                  children: <Widget>[
                    new Checkbox(
                      value: _usaMail,
                      activeColor: Colors.black,
                      onChanged: (value) {
                        setState(() {
                          _usaMail = value;
                        });
                      },
                    ),
                  ],
                ),
              ),
              title: mail,
            ),
            //...
            new Divider()
          ],
        );
      }
    }
    

    查看Flutter存储库中的官方示例总是有帮助的!

  • 1

    是的,理论上可以使用 GlobalKeybut not recommended!

    class OuterWidget extends StatefulWidget {
      @override
      State<StatefulWidget> createState() => OuterWidgetState();
    }
    
    class OuterWidgetState extends State<OuterWidget> {
      final _innerKey = GlobalKey<InnerWidgetState>();
    
      @override
      Widget build(BuildContext context) {
        return Column(
          children: <Widget>[
            InnerWidget(key: _innerKey),
            RaisedButton(
              child: Text('call foo'),
              onPressed: () {
                _innerKey.currentState.foo();
              },
            )
          ],
        );
      }
    }
    
    class InnerWidget extends StatefulWidget {
      InnerWidget({Key key}) : super(key: key);
    
      @override
      State<StatefulWidget> createState() => InnerWidgetState();
    }
    
    class InnerWidgetState extends State<InnerWidget> {
      String _value = 'not foo';
    
      @override
      Widget build(BuildContext context) {
        return Text(_value);
      }
    
      void foo() {
        setState(() {
          _value = 'totally foo';
        });
      }
    }
    

    更好的方法:相反,拉动状态是个好主意:

    class OuterWidget extends StatefulWidget {
      @override
      State<StatefulWidget> createState() => OuterWidgetState();
    }
    
    class OuterWidgetState extends State<OuterWidget> {
      String _innerValue;
    
      @override
      Widget build(BuildContext context) {
        return Column(
          children: <Widget>[
            InnerWidget(value: _innerValue),
            RaisedButton(
              child: Text('call foo'),
              onPressed: () {
                setState(() {
                  _innerValue = 'totally foo';
                });
              },
            )
          ],
        );
      }
    }
    
    class InnerWidget extends StatefulWidget {
      InnerWidget({Key key, this.value}) : super(key: key);
    
      final String value;
    
      @override
      State<StatefulWidget> createState() => InnerWidgetState();
    }
    
    class InnerWidgetState extends State<InnerWidget> {
      @override
      Widget build(BuildContext context) {
        return Text(widget.value);
      }
    }
    

    如果可以,使内部小部件无状态:

    class InnerWidget extends StatelessWidget {
      InnerWidget({Key key, this.value}) : super(key: key);
    
      final String value;
    
      @override
      Widget build(BuildContext context) {
        return Text(value);
      }
    }
    

    如果您的孩子是交互式的(点击,复选框...),您可以使用 VoidCallbackValueChanged<T> (或您自己的 typedef )定义回调来处理父窗口小部件中的事件 .

相关问题