You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter自定义组件状态问题:父/内部更新异常、TouchPoint交互故障排查

本篇内容较长,我暂未定位到问题根源,也不清楚该如何处理。

更新说明:若改用StatelessWidget,数值可以正常同步,因此……

设计目标

我设计的TouchPoint核心是一个按钮,点击拖拽时会将事件向上传递给可滚动容器;长按则进入编辑模式,可处理多类数据;点击弹窗确认按钮会调用父组件的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(
      // 这是页面显示的按钮
      style: ButtonStyle(
          backgroundColor: MaterialStateProperty.all<Color>(
              enabled ? Colors.blue : Colors.grey),
          foregroundColor: MaterialStateProperty.all(Colors.white)),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.start, // TODO: 要不要居中?
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          icon,
          Column(
              // TODO: 这里的对齐设置好像不生效
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                Text(
                  title,
                  textAlign:
                      TextAlign.center, // TODO: 文本对齐也不生效
                ),
                _uiControl(inEdit: false, type: type, value: value),
              ])
        ],
      ),
      onPressed: null, // TODO: 把点击拖拽事件传给父组件
      onLongPress: !enabled
          ? null
          : () {
              // TODO: 这里弹出设置组件
              print(
                  "按下:" + title + " [" + type.toString() + ": $value ]");

              _inputDialog(context: context, touchPoint: this).then((retval) {
                if (retval != null) {
                  // 点击了确认按钮
                  print("设置值:" + 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("编辑状态 $inEdit, 值: $value, " +
        ((onPressed == null) ? "无回调" : "有回调"));
  switch (type) {
    case InputType.numeric:
    case InputType.text:
      if (!inEdit)
        return Text(value);
      else {
        Widget textf = TextField(
          autofocus: true,
          keyboardType: // TODO: Flutter官方bug,数字键盘相关问题
              (type == InputType.numeric)
                  ? TextInputType.number
                  : TextInputType.text,
          // TODO: 限制最大长度
          // TODO: 要不要限制数值范围?
          controller: _textFieldController,
          decoration: InputDecoration(hintText: "zzBogusHint"),
        );

        // 设置初始值(只能通过控制器设置)
        switch (type) {
          case InputType.numeric:
          case InputType.text:
            _textFieldController.text = value;
            _textFieldController.selection = TextSelection.fromPosition(
                TextPosition(offset: _textFieldController.text.length));
            break;
          default: // 这几个类型不需要键盘
            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(
              // 仅做样式展示用,不可交互
              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);
  }
}

// 用于修改值的弹窗
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, // 弹窗大小自适应内容
              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("布尔值更新: $myval");
                          break;
                        case InputType.outconfig:
                          int ival = int.parse(myval);
                          print("NO/NC切换前: $str - $ival");

                          ival =
                              ((ival & 0xFF00) + ((ival & 0xFF) == 0 ? 1 : 0));
                          myval = ival.toString();
                          print("NO/NC切换后: $ival");
                          break;
                        default:
                      }
                    },
                  )
              ]),
          actions: <Widget>[
            FlatButton(
              color: Colors.red,
              textColor: Colors.white,
              child: Text('取消'),
              onPressed: () {
                Navigator.pop(context);
              },
            ),
            FlatButton(
              color: Colors.green,
              textColor: Colors.white,
              child: Text('确认'),
              onPressed: () {
                // 需要从控制器获取TextField的输入值
                String retval;
                switch (touchPoint.type) {
                  case InputType.numeric:
                  case InputType.text:
                    retval = _textFieldController.text;
                    break;
                  default:
                    retval = myval;
                }
                Navigator.pop(context, retval);
              },
            ),
          ],
        );
      });

  return dlg;
}

内容的提问来源于stack exchange,提问作者ThorMJ

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.21 17:45:05