TextField上的TextController在编辑一次后不再监听更改&& OnChanged不再起作用

时间:2019-03-30 16:39:45

标签: dart flutter

我希望能够编辑TextField,并在所有文本填充后,计算出一个值。每次用户更改值时,都必须重新计算该值。

我已经尝试了Streams和Controller。数据流运行良好,即使使用旧的1个数据更新值也是如此。 即heightweightage都必须为!= null,当我们最初拥有{height: 2, weight:3, age:2}时,其中一个是更改后,将使用上述数据计算结果。 相反,控制器似乎根本没有侦听变量更改。

在这里我在另一个类中初始化控制器

class Bsmi extends StatelessWidget {
  StreamController<void> valueBoxStream = StreamController<void>.broadcast();

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new DefaultAppBar(context: context, title: Text(" ")),
      body: new ListView(
        children: <Widget>[
          BsmiResult(valueBoxStream, {}),
          new Container(
            alignment: Alignment.topLeft,
            padding: EdgeInsets.all(20),
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(15.0),
            ),
            child: ListView(
              shrinkWrap: true,
              physics: ClampingScrollPhysics(),
              padding: EdgeInsets.all(20.0),
              itemExtent: 80.0,
              children: <Widget>[
                _BsmiResultState().indices("height", valueBoxStream),
                _BsmiResultState().indices("weight", valueBoxStream),
                _BsmiResultState().indices("age", valueBoxStream),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

这是重要的代码

class BsmiResult extends StatefulWidget {
  final StreamController<void> valueBoxStream;

  const BsmiResult(this.valueBoxStream, this.data);

  final Map data;

  @override
  _BsmiResultState createState() =>
      _BsmiResultState(valueBoxStream: this.valueBoxStream, data: this.data);
}

class _BsmiResultState extends State<BsmiResult> {
  _BsmiResultState({this.valueBoxStream, this.data});
  StreamController<void> valueBoxStream;

  final Map data;

  final textFieldController = new TextEditingController();

  int weight;
  int age;
  int height;
  String _result = "0.0";



  _printLatestValue() {
    print("Second text field: ${textFieldController.text}");
  }

  void setData(data) {
    if (data['type'] == 'height') {
      print("I'm inside height\n");
      height = int.parse(data['value']);
    } else if (data['type'] == 'weight') {
      print("I'm inside weight\n");
      weight = int.parse(data['value']);
    } else {
      print("I'm inside age\n");
      age = int.parse(data['value']);
    }
  }

  @override
  void initState() {
    super.initState();
    textFieldController.addListener(_printLatestValue);
    valueBoxStream.stream.listen((_){
      _calculateResult();
    });
  }

  @override
  void dispose() {
    // Clean up the controller when the Widget is removed from the Widget tree
    textFieldController.dispose();
    super.dispose();
  }

  void _calculateResult() {
      if ((height != null) && (height != 0) && (weight != null) && (weight != 0) && (age != null) && (age != 0)) {
        print("height: " +
            height.toString() +
            "" +
            " weight: " +
            weight.toString() +
            " age: " +
            age.toString());
        _result = ((height + weight) / age).toString();
        print(_result + "\n");
      } else {
        _result = "0.0";
      }
  }

  @override
  Widget build(BuildContext context) {
    return new Column(
      children: <Widget>[
        new Text("Risultato"),
        new Container(
          decoration: new BoxDecoration(
            borderRadius: BorderRadius.circular(15.0),
          ), //For the controller i don't use the StreamBuilder but a normal Text("$_result"),
          child: StreamBuilder(  
            stream: valueBoxStream.stream,
            builder: (context, snapshot) {
              if (snapshot.hasData || _result !=  "0.0") {
                print(snapshot.data);
                setData(snapshot.data);
                return new Text(
                  "$_result",
                  textAlign: TextAlign.end,
                );
              } else {
                return new Text(
                  "0.0",
                  textAlign: TextAlign.end,
                );
              }
            },
          ),
        ),
      ],
    );
  }

  Row indices(String text, StreamController<void> valueBoxStream) {
    return new Row(
      children: <Widget>[
        leftSection(text),
        rightSection(text, valueBoxStream),
      ],
    );
  }

  Widget leftSection(text) {
    return new Expanded(
      child: new Container(
        child: new Text(
          text,
          style: TextStyle(fontSize: 18),
        ),
      ),
    );
  }


  Container rightSection(text, valueBoxStream) {
    return new Container(
      child: new Flexible(
        flex: 1,
        child: new TextField(
          controller: textFieldController,
          keyboardType: TextInputType.number,
          textAlign: TextAlign.end,
          inputFormatters: <TextInputFormatter>[
            WhitelistingTextInputFormatter(new RegExp("[0-9.]")),
            LengthLimitingTextInputFormatter(4),
          ],
          decoration: new InputDecoration(
            enabledBorder: new OutlineInputBorder(
              borderRadius: new BorderRadius.circular(15),
              borderSide: new BorderSide(width: 1.2),
            ),
            border: new OutlineInputBorder(
              borderRadius: new BorderRadius.circular(15),
              borderSide: new BorderSide(color: Colors.lightBlueAccent),
            ),
            hintText: '0.0',
          ),
          onChanged: (val) {
            valueBoxStream.add({'type': text, 'value': val});
          },
        ),
      ),
    );
  }
}

在这一点上我应该能够得到Result: x.y,但是在同一个'session'中一次只更改一次textField之后,我只能得到一次。

预先感谢谁能真正向我解释为什么这不起作用以及我犯了什么错误。

1 个答案:

答案 0 :(得分:0)

我真的很抱歉..但是该代码有很多错误,我什至不知道从哪里开始。您似乎已阅读了有关如何处理抖动状态的所有版本,并设法在一个简单的用例中将所有状态组合在一起。这是一个有效的示例修复:https://gitlab.com/hpoul/clinimetric/commit/413d32d4041f2e6adb7e30af1126af4b05e72271

我不确定如何回答这个“问题”,以便其他人可以从中受益。但是..

  1. 如果您创建一个流控制器或一个文本视图控制器..您具有状态..因此,请创建一个有状态的小部件(否则您将无法关闭流和订阅)
  2. 获取有关哪个窗口小部件负责哪个状态的句柄。并将仅有用的值传递给子视图。一种可能的架构是使用ViewController并使用接收器作为更改事件的输入,并使用Streams处理这些事件。因此,如果创建流控制器,则仅在要通知事件时才使用streamController.sink,而在小部件需要侦听事件时才使用streamController.stream
  3. build方法不是处理事件的正确位置。我重构了您的代码,以在流的侦听器中完成事件的处理和事件的计算。如果要使用StreamBuilder,则可以有一个StreamController,生产者可以直接将结果推入接收器..因此,StreamBuilder的builder方法只需要执行Text('$ {snapshot.data.value}')< / li>

该列表中可能缺少很多东西。但这应该可以帮助您入门。看看我的零钱集。它不是完美的,但是有效。您应该尝试了解自己在做什么并降低复杂性,而不是将发现的所有内容堆叠在一起。