状态类的调用方法

时间:2018-08-22 00:15:24

标签: flutter

给定一个有状态的小部件,可以某种方式调用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(){...}
}

编辑:一些真实代码

首先,我们将其与内部小部件等效;这是一个自定义的文本字段。关键是我想根据布尔_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);
}

最后,相当于我的外部窗口小部件。这只是一个复选框,后面是prevoius EntradaEmail小部件。据我所知,一旦按下复选框并进行了onChange调用,则setState调用应重新生成所有内容,但与debug对比的是,从未调用过第一个内部小部件的build方法。我的意思是根据复选框启用和禁用文本字段。

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 个答案:

答案 0 :(得分:1)

是的,理论上可以使用GlobalKey但不推荐!

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)定义回调以处理父项中的事件小部件。

答案 1 :(得分:1)

好的,既然您已经添加了示例代码,我将尝试解释为什么您的小部件不起作用,并且我将尝试解释还有哪些其他改进。


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


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

通常,您将从父窗口小部件(在您提交数据时处理数据的窗口小部件)中传入TextEditingController


此外,不建议扩展小部件。而是使用合成,例如:

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

窗口小部件属性应始终是公开的和最终的(绝不能以_开头)。


您在CampoEnvio中做了一些奇怪的事情。

首先,由于某种原因,您要将小部件的所有属性传递到State中的createState。这会带来一些您可能不希望的后果。

通常,您的State类具有构造函数参数是非常少的,并且通常将属性从有状态小部件传递给状态。

问题在于createState仅被调用一次,而在父窗口小部件中调用initState时不会再次调用它。状态一直保持到小部件被处置为止。

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


您还很需要在EntradaMail中创建小部件(EntradaMovilStatefulWidget)。

扩展StatefulWidget的类不应该这样做!它基本上只是属性的“包”。


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

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存储库中查看官方样本总是有帮助的!