首页 > 解决方案 > 小部件的有状态问题——从父级更新和从内部更新

问题描述

这将是太长了,但我不知道问题出在哪里/该怎么做。

更新:如果我改用 StateLessWidget,值会正确跟踪;所以...

目的

在它的核心,我设想我的 TouchPoint 是一个按钮,如果点击并拖动它会将拖动传递到可滚动容器。如果 LongPressed,它将进入编辑模式并处理几种不同类型的数据;如果在对话框上按下 OK,它将调用 parent() OnPressed 函数将新值传回。如果按下取消,则不应调用 onpressed 或更新标签。

问题

当对话框打开时,你不能真正与之交互(我在控件上有打印消息,我看到它被点击了,但是对话框中的 UI 控件没有反映“新”状态)。

您无法点击并拖动按钮

代码

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'statusword_t.dart';

enum InputType { boolean, text, numeric, outconfig, sword }

class TouchPoint extends StatelessWidget {
  TouchPoint({
    Key? key,
    required this.title,
    required this.icon,
    required this.value,
    required this.enabled,
    required this.type,
    this.onlyButtons = false,
    this.warning,
    this.setter,
  }) : super(key: key);

  final String title;
  final String value;
  final Icon icon;
  final bool enabled;
  final InputType type;
  final bool onlyButtons;
  final Function(String)? setter;
  final String? warning;

  /*@override
  _TouchPointState createState() => _TouchPointState();
  */
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      //This is the screen buton
      style: ButtonStyle(
          backgroundColor: MaterialStateProperty.all<Color>(
              enabled ? Colors.blue : Colors.grey),
          foregroundColor: MaterialStateProperty.all(Colors.white)),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.start, //ToDo: center?
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          icon,
          Column(
              //ToDo: Alignment here doesn't seem to do anything...
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                Text(
                  title,
                  textAlign:
                      TextAlign.center, //ToDo: TextAlign doesn't work either...
                ),
                _uiControl(inEdit: false, type: type, value: value),
              ])
        ],
      ),
      onPressed: null, //ToDo: Pass click-drag to parent...
      onLongPress: !enabled
          ? null
          : () {
              //ToDo: Flyout setter widget here.
              print(
                  "Pressed: " + title + " [" + type.toString() + ": $value ]");

              _inputDialog(context: context, touchPoint: this).then((retval) {
                if (retval != null) {
                  //OK was pressed
                  print("Setter:" + retval);
                  if (setter != null) setter!(retval);
                }
              });
            },
    );
  }
}

TextEditingController _textFieldController = TextEditingController();

Widget _uiControl(
    {required bool inEdit,
    required InputType type,
    required String value,
    Function(String)? onPressed,
    bool? printme}) {
  if (printme != null)
    print("IE $inEdit, val: $value, " +
        ((onPressed == null) ? "nocall" : "wcall"));
  switch (type) {
    case InputType.numeric:
    case InputType.text:
      if (!inEdit)
        return Text(value);
      else {
        Widget textf = TextField(
          autofocus: true,
          keyboardType: //ToDo: Flutter Bug https://github.com/flutter/flutter/issues/58510
              (type == InputType.numeric)
                  ? TextInputType.number
                  : TextInputType.text,
          //ToDo: maxLength: maxlen,
          //ToDo: Min/Max value?
          controller: _textFieldController,
          decoration: InputDecoration(hintText: "zzBogusHint"),
        );

        //Set Initial Values (since it has to be done via the Controller)
        switch (type) {
          case InputType.numeric:
          case InputType.text:
            _textFieldController.text = value;
            _textFieldController.selection = TextSelection.fromPosition(
                TextPosition(offset: _textFieldController.text.length));
            break;
          default: //These aren't keyboards
            break;
        }
        return textf;
      }
    case InputType.boolean:
      return SizedBox(
          height: 16,
          width: 16,
          child: Checkbox(
            value: value == "1",
            onChanged: onPressed == null
                ? null
                : (val) {
                    onPressed(value);
                  },
          ));
    case InputType.outconfig:
      return !inEdit
          ? ToggleButtons(
              //We're gonna look like toggle buttons, but it's just a statement of what we are.
              disabledColor: Colors.white,
              color: Colors.white,
              disabledBorderColor: Colors.white,
              children: [
                int.parse(value) % 2 == 0 ? Text("NO") : Text("NC"),
              ],
              constraints: BoxConstraints(minHeight: 16, minWidth: 32),
              borderRadius: BorderRadius.circular(8.0),
              borderWidth: 1,
              isSelected: [true])
          : Row(
              children: [
                ToggleButtons(
                    children: [Text("NO"), Text("NC")],
                    constraints: BoxConstraints(minHeight: 16, minWidth: 32),
                    borderRadius: BorderRadius.circular(8.0),
                    borderWidth: 1,
                    isSelected: [
                      int.parse(value) % 2 == 0,
                      int.parse(value) % 2 == 1
                    ],
                    onPressed: onPressed == null
                        ? null
                        : (index) => onPressed(index.toString())),
              ],
            );
    case InputType.sword:
      return StatusWordWidget(int.parse(value),
          short: !inEdit, direction: !inEdit ? Axis.horizontal : Axis.vertical);
  }
}

//A flyout dialog for changing values
Future<String?> _inputDialog(
    {required BuildContext context, required TouchPoint touchPoint}) async {
  String myval = touchPoint.value;
  final dlg = showDialog<String>(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: Text(touchPoint.title),
          content: Column(mainAxisSize: MainAxisSize.min, //Keep it small
              children: [
                if (touchPoint.warning != null) Text(touchPoint.warning!),
                if (!touchPoint.onlyButtons)
                  _uiControl(
                    printme: true,
                    inEdit: true,
                    type: touchPoint.type,
                    value: myval,
                    onPressed: (str) {
                      switch (touchPoint.type) {
                        case InputType.boolean:
                          myval = myval == "0" ? "1" : "0";
                          print("BoolUpdate: $myval");
                          break;
                        case InputType.outconfig:
                          int ival = int.parse(myval);
                          print("NoncB4: $str - $ival");

                          ival =
                              ((ival & 0xFF00) + ((ival & 0xFF) == 0 ? 1 : 0));
                          myval = ival.toString();
                          print("NoncAfter: $ival");
                          break;
                        default:
                      }
                    },
                  )
              ]),
          actions: <Widget>[
            FlatButton(
              color: Colors.red,
              textColor: Colors.white,
              child: Text('CANCEL'),
              onPressed: () {
                Navigator.pop(context);
              },
            ),
            FlatButton(
              color: Colors.green,
              textColor: Colors.white,
              child: Text('OK'),
              onPressed: () {
                //You have to get the value of the TextField from the Controller
                String retval;
                switch (touchPoint.type) {
                  case InputType.numeric:
                  case InputType.text:
                    retval = _textFieldController.text;
                    break;
                  default:
                    retval = myval;
                }
                Navigator.pop(context, retval);
              },
            ),
          ],
        );
      });

  return dlg;
}

标签: flutterstatefulwidgetstatelesswidget

解决方案


真正的答案是我需要拆分它:主小部件是无状态的 - 所以它会自动更新。

“编辑对话框”是一个单独的 StateFulWidget - 因此您可以与之交互,但值是在创建时设置的。

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'statusword_t.dart';

enum InputType { boolean, text, numeric, outconfig, sword }

class TouchPoint extends StatelessWidget {
  TouchPoint({
    Key? key,
    required this.title,
    required this.icon,
    required this.value,
    required this.enabled,
    required this.type,
    this.onlyButtons = false,
    this.warning,
    this.setter,
  }) : super(key: key);

  final String title;
  final String value;
  final Icon icon;
  final bool enabled;
  final InputType type;
  final bool onlyButtons;
  final Function(String)? setter;
  final String? warning;

  /*@override
  _TouchPointState createState() => _TouchPointState();
  */
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      //This is the screen buton
      style: ButtonStyle(
          backgroundColor: MaterialStateProperty.all<Color>(
              enabled ? Colors.blue : Colors.grey),
          foregroundColor: MaterialStateProperty.all(Colors.white)),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.start, //ToDo: center?
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          icon,
          Column(
              //ToDo: Alignment here doesn't seem to do anything...
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                Text(
                  title,
                  textAlign:
                      TextAlign.center, //ToDo: TextAlign doesn't work either...
                ),
                _uiControl(inEdit: false, type: type, value: value),
              ])
        ],
      ),
      onPressed: null, //ToDo: Pass click-drag to parent...
      onLongPress: !enabled
          ? null
          : () {
              //ToDo: Flyout setter widget here.

              _inputDialog(context: context, touchPoint: this).then((retval) {
                if (retval != null) {
                  //OK was pressed
                  if (setter != null) setter!(retval);
                }
              });
            },
    );
  }
}

TextEditingController _textFieldController = TextEditingController();

Widget _uiControl(
    {required bool inEdit,
    required InputType type,
    required String value,
    Function(String)? onPressed}) {
  switch (type) {
    case InputType.numeric:
    case InputType.text:
      if (!inEdit)
        return Text(value);
      else {
        Widget textf = TextField(
          autofocus: true,
          keyboardType: //ToDo: Flutter Bug https://github.com/flutter/flutter/issues/58510
              (type == InputType.numeric)
                  ? TextInputType.number
                  : TextInputType.text,
          //ToDo: maxLength: maxlen,
          //ToDo: Min/Max value?
          controller: _textFieldController,
          decoration: InputDecoration(hintText: "zzBogusHint"),
        );

        //Set Initial Values (since it has to be done via the Controller)
        switch (type) {
          case InputType.numeric:
          case InputType.text:
            _textFieldController.text = value;
            _textFieldController.selection = TextSelection.fromPosition(
                TextPosition(offset: _textFieldController.text.length));
            break;
          default: //These aren't keyboards
            break;
        }
        return textf;
      }
    case InputType.boolean:
      return SizedBox(
          height: 16,
          width: 16,
          child: Checkbox(
            value: value == "1",
            onChanged: onPressed == null
                ? null
                : (val) {
                    onPressed(value);
                  },
          ));
    case InputType.outconfig:
      return !inEdit
          ? ToggleButtons(
              //We're gonna look like toggle buttons, but it's just a statement of what we are.
              disabledColor: Colors.white,
              color: Colors.white,
              disabledBorderColor: Colors.white,
              children: [
                int.parse(value) % 2 == 0 ? Text("NO") : Text("NC"),
              ],
              constraints: BoxConstraints(minHeight: 16, minWidth: 32),
              borderRadius: BorderRadius.circular(8.0),
              borderWidth: 1,
              isSelected: [true])
          : Row(
              children: [
                ToggleButtons(
                    children: [Text("NO"), Text("NC")],
                    constraints: BoxConstraints(minHeight: 16, minWidth: 32),
                    borderRadius: BorderRadius.circular(8.0),
                    borderWidth: 1,
                    isSelected: [
                      int.parse(value) % 2 == 0,
                      int.parse(value) % 2 == 1
                    ],
                    onPressed: onPressed == null
                        ? null
                        : (index) => onPressed(index.toString())),
              ],
            );
    case InputType.sword:
      return StatusWordWidget(int.parse(value),
          short: !inEdit, direction: !inEdit ? Axis.horizontal : Axis.vertical);
  }
}

/* The dialog, since it changes the vaues, needs to be a stateful widget */
class _TouchPointDialog extends StatefulWidget {
  //ToDo: Pass in things here
  _TouchPointDialog({Key? key, required this.touchPoint}) : super(key: key);

  final TouchPoint touchPoint;
  @override
  _TouchPointDialogState createState() =>
      _TouchPointDialogState(touchPoint.value);
}

class _TouchPointDialogState extends State<_TouchPointDialog> {
  _TouchPointDialogState(this.myval);
  String myval;

  @override
  Widget build(BuildContext context) {
    final dlg = AlertDialog(
      title: Text(widget.touchPoint.title),
      content: Column(mainAxisSize: MainAxisSize.min, //Keep it small
          children: [
            if (widget.touchPoint.warning != null)
              Text(widget.touchPoint.warning!),
            if (!widget.touchPoint.onlyButtons)
              _uiControl(
                inEdit: true,
                type: widget.touchPoint.type,
                value: myval,
                onPressed: (str) {
                  setState(() {
                    switch (widget.touchPoint.type) {
                      case InputType.boolean:
                        myval = myval == "0" ? "1" : "0";
                        break;
                      case InputType.outconfig:
                        int ival = int.parse(myval);
                        ival = ((ival & 0xFF00) + ((ival & 0xFF) == 0 ? 1 : 0));
                        myval = ival.toString();
                        break;
                      default:
                    }
                  });
                },
              )
          ]),
      actions: <Widget>[
        FlatButton(
          color: Colors.red,
          textColor: Colors.white,
          child: Text('CANCEL'),
          onPressed: () {
            Navigator.pop(context);
          },
        ),
        FlatButton(
          color: Colors.green,
          textColor: Colors.white,
          child: Text('OK'),
          onPressed: () {
            //You have to get the value of the TextField from the Controller
            String retval;
            switch (widget.touchPoint.type) {
              case InputType.numeric:
              case InputType.text:
                retval = _textFieldController.text;
                break;
              default:
                retval = myval;
            }
            Navigator.pop(context, retval);
          },
        ),
      ],
    );

    return dlg;
  }
}

//A flyout dialog for changing values
Future<String?> _inputDialog(
    {required BuildContext context, required TouchPoint touchPoint}) async {
  return showDialog(
      barrierDismissible: false,
      context: context,
      builder: (BuildContext context) {
        return _TouchPointDialog(touchPoint: touchPoint);
      });
}


推荐阅读