给定一个有状态的小部件,可以某种方式调用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()
],
);
}
}
答案 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);
}
}
如果您的孩子是互动的(轻击,复选框...),则可以使用VoidCallback
或ValueChanged<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
时不会再次调用它。状态一直保持到小部件被处置为止。
这意味着您的状态构造函数也仅被调用一次,_State
(CampoEnvio
中)中的字段将始终保持不变。即使重新构造了父级并再次调用CampoEnvio
的构造函数,_State
中的旧值也不会被替换。
您还很需要在EntradaMail
中创建小部件(EntradaMovil
和StatefulWidget
)。
扩展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存储库中查看官方样本总是有帮助的!