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

Flutter自定义表单:如何修改列表内指定Widget的属性?

问题:实现自定义表单中已添加Widget的属性编辑功能

我有一个Widget列表,希望修改列表内指定Widget的属性。目前已实现Flutter自定义表单功能:左侧展示预定义Widget列表,点击“+ Add”按钮可将对应Widget实例添加到右侧表单区域(效果如图:自定义表单效果)。现在需要实现右侧已添加Widget的属性编辑功能,例如设置文本加粗、修改TextField内容、更改按钮文本及动态定义onPressed事件等。

现有代码

PredfinedFileds.dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class PreDefinedFields{

  static Text textCustom= Text("New Text");
  static TextFormField textFieldCustom= TextFormField(
  );
  static ElevatedButton elevatedButtonCustom= ElevatedButton(onPressed: (){}, child: Text("New Button"));
  
}

CustomizableForm.dart

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'PreDefinedFields.dart';

class CustomizableForm extends StatefulWidget {
  const CustomizableForm({Key? key}) : super(key: key);

  @override
  State<CustomizableForm> createState() => _CustomizableFormState();
}

class _CustomizableFormState extends State<CustomizableForm> {
  static bool isBold=false;
  List fieldList=[];
  List widgetList=[];

  var text;

  @override
  Widget build(BuildContext context) {

void ShowDilogText(isBold){



  showGeneralDialog(
    context: context,
    barrierLabel: "Barrier",
    barrierDismissible: true,
    barrierColor: Colors.black.withOpacity(0.5),
    transitionDuration: Duration(milliseconds: 700),
    pageBuilder: (_, __, ___) {
      return Center(
        child: Container(
          height: 440,
          width: MediaQuery.of(context).size.width/2,
          child: SizedBox(child: Container(
            child: Center(child: ElevatedButton(
              onPressed: (){
                isBold != isBold;
              },
              child: Text("Bold"),
            ),),
          )),
          margin: EdgeInsets.symmetric(horizontal: 20),
          decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(40)),
        ),
      );
    },
    transitionBuilder: (_, anim, __, child) {
      Tween<Offset> tween;
      if (anim.status == AnimationStatus.reverse) {
        tween = Tween(begin: Offset(-1, 0), end: Offset.zero);
      } else {
        tween = Tween(begin: Offset(1, 0), end: Offset.zero);
      }

      return SlideTransition(
        position: tween.animate(anim),
        child: FadeTransition(
          opacity: anim,
          child: child,
        ),
      );
    },
  );
}



TextEditingController dateController=  TextEditingController();
    TextField datePickerField=TextField(
        controller: dateController, //editing controller of this TextField
        decoration: const InputDecoration(
            icon: Icon(Icons.calendar_today), //icon of text field
            labelText: "Enter Date" //label text of field
        ),
        readOnly: true,  // when true user cannot edit text
        onTap: () async {
          //when click we have to show the datepicker
          DateTime? pickedDate = await showDatePicker(
              context: context,
              initialDate: DateTime.now(), //get today's date
              firstDate:DateTime(2000), //DateTime.now() - not to allow to choose before today.
              lastDate: DateTime(2101)
          );
          if(pickedDate != null ){
            print(pickedDate);  //get the picked date in the format => 2022-07-04 00:00:00.000
            String formattedDate = DateFormat('yyyy-MM-dd').format(pickedDate); // format date in required form here we use yyyy-MM-dd that means time is removed
            print(formattedDate); //formatted date output using intl package =>  2022-07-04
            //You can format date as per your need

            setState(() {
              dateController.text = formattedDate; //set foratted date to TextField value.
            });
          }else{
            print("Date is not selected");
          }
        }
    );





    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Row(
          children: [
//For Predefined Fields
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: SizedBox(
                width: MediaQuery.of(context).size.width/3,
                child: Column(
                  children: [
                    ListTile(
                      leading: const Icon(Icons.text_fields),
                      title: const Text("Text"),
                      trailing:  ElevatedButton(onPressed: (){
                        fieldList.add('Text');
                        widgetList.add(PreDefinedFields.textCustom);
                        setState(() {
                          print(fieldList);
                        });
                      }, child: Text("+ Add")),
                    ),
                    ListTile(
                      leading: const Icon(Icons.text_fields),
                      title: const Text("Text Field"),
                      trailing:  ElevatedButton(onPressed: (){
                        fieldList.add('TextField');
                        widgetList.add(PreDefinedFields.textFieldCustom);
                        setState(() {
                          print(fieldList);
                        });
                      }, child: Text("+ Add")),
                    ),
                    ListTile(
                      leading: const Icon(Icons.text_fields),
                      title: const Text("Button"),
                      trailing:  ElevatedButton(onPressed: (){
                        fieldList.add('Button');
                        widgetList.add(PreDefinedFields.elevatedButtonCustom);
                        setState(() {
                          print(fieldList);
                        });
                      }, child: Text("+ Add")),
                    ),

                    ListTile(
                      leading: const Icon(Icons.text_fields),
                      title: const Text("Date"),
                      trailing:  ElevatedButton(onPressed: (){
                        fieldList.add('Date Picker');
                        widgetList.add(datePickerField);
                        setState(() {
                          print(fieldList);
                        });
                      }, child: Text("+ Add")),
                    ),
                  ],
                ),
              ),
            ),
            //For Form
            Column(
              crossAxisAlignment: CrossAxisAlignment.center,

              children: [
                Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Container(
                    height: MediaQuery.of(context).size.height-50,
                    width: MediaQuery.of(context).size.width/2,
                    decoration: BoxDecoration(
                        border: Border.all(color: Colors.blueAccent)
                    ),
                  child: Form(
                      child: ListView.builder(
                          shrinkWrap: true,
                          itemCount: fieldList.length,
                          itemBuilder: (context,index){
                            if(fieldList[index]=='Text'){
                              return Padding(
                                padding: const EdgeInsets.all(8.0),
                                child: Row(
                                  children: [
                                    PreDefinedFields.textCustom,
                                    Spacer(),
                                    IconButton(onPressed: (){
                                      ShowDilogText(isBold);
                                      setState(() {
                                        final tile = widgetList.firstWhere((item) => item[index]);

                                      });
                                    }, icon: Icon(Icons.edit)),
                                    IconButton(onPressed: (){
                                    }, icon: Icon(Icons.delete,color: Colors.red,))
                                  ],
                                ),
                              );
                            }

                            if(fieldList[index]=='Date Picker'){
                              return Padding(
                                padding: const EdgeInsets.all(8.0),
                                child: Row(
                                  children: [
                                    SizedBox(
                                        width: 400,
                                        child: datePickerField),
                                    Spacer(),
                                    IconButton(onPressed: (){}, icon: Icon(Icons.edit)),
                                    IconButton(onPressed: (){}, icon: Icon(Icons.delete,color: Colors.red,))
                                  ],
                                ),
                              );
                            }
                            if(fieldList[index]=='TextField'){
                              return Padding(
                                padding: const EdgeInsets.all(8.0),
                                child: Row(
                                  children: [
                                    SizedBox(
                                        width: 400,
                                        child: PreDefinedFields.textFieldCustom),
                                    Spacer(),
                                    IconButton(onPressed: (){}, icon: Icon(Icons.edit)),
                                    IconButton(onPressed: (){}, icon: Icon(Icons.delete,color: Colors.red,))
                                  ],
                                ),
                              );
                            }
                            if(fieldList[index]=='Button'){
                              return Padding(
                                padding: const EdgeInsets.all(8.0),
                                child: Row(
                                  children: [
                                    PreDefinedFields.elevatedButtonCustom,
                                    Spacer(),
                                    IconButton(onPressed: (){}, icon: Icon(Icons.edit)),
                                    IconButton(onPressed: (){}, icon: Icon(Icons.delete,color: Colors.red,))
                                  ],
                                ),
                              );
                            }
                            return Container();
                          })

                  ),
              ),
                )],
            ),
          ],
        ),
      ),
    );
  }


}

解决方案

1. 重构预定义Widget为工厂方法,避免共享实例

当前代码使用static Widget实例会导致所有添加的同类型Widget共享状态,修改属性会影响全部实例。改为工厂方法每次生成新实例:

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class PreDefinedFields{
  // 文本组件工厂方法,支持自定义文本和样式
  static Text createText({String text = "New Text", TextStyle? style}) {
    return Text(text, style: style);
  }

  // 输入框组件工厂方法,支持自定义控制器和装饰
  static TextFormField createTextField({TextEditingController? controller, InputDecoration? decoration}) {
    return TextFormField(
      controller: controller,
      decoration: decoration ?? const InputDecoration(),
    );
  }

  // 按钮组件工厂方法,支持自定义文本和点击事件
  static ElevatedButton createButton({String text = "New Button", VoidCallback? onPressed}) {
    return ElevatedButton(onPressed: onPressed ?? (){}, child: Text(text));
  }
}

2. 创建数据模型存储Widget属性

为每种Widget定义属性模型,用来保存可编辑参数:

// 文本组件属性模型
class TextWidgetData {
  String text;
  bool isBold;

  TextWidgetData({this.text = "New Text", this.isBold = false});
}

// 输入框组件属性模型
class TextFieldWidgetData {
  String hintText;
  TextEditingController controller;

  TextFieldWidgetData({this.hintText = "", required this.controller});
}

// 按钮组件属性模型
class ButtonWidgetData {
  String text;
  VoidCallback? onPressed;

  ButtonWidgetData({this.text = "New Button", this.onPressed});
}

// 日期选择器属性模型
class DatePickerWidgetData {
  String labelText;
  TextEditingController controller;

  DatePickerWidgetData({this.labelText = "Enter Date", required this.controller});
}

3. 修改状态管理,存储属性模型而非Widget

在_CustomizableFormState中,用列表存储每个组件的类型和对应属性模型:

class _CustomizableFormState extends State<CustomizableForm> {
  // 存储每个组件的类型和属性数据
  List<Map<String, dynamic>> formItems = [];

  // 添加文本组件
  void addText() {
    setState(() {
      formItems.add({
        "type": "Text",
        "data": TextWidgetData(),
      });
    });
  }

  // 添加输入框组件
  void addTextField() {
    setState(() {
      formItems.add({
        "type": "TextField",
        "data": TextFieldWidgetData(controller: TextEditingController()),
      });
    });
  }

  // 添加按钮组件
  void addButton() {
    setState(() {
      formItems.add({
        "type": "Button",
        "data": ButtonWidgetData(),
      });
    });
  }

  // 添加日期选择器组件
  void addDatePicker() {
    setState(() {
      formItems.add({
        "type": "Date Picker",
        "data": DatePickerWidgetData(controller: TextEditingController()),
      });
    });
  }

4. 实现编辑对话框修改属性

以文本组件为例,创建编辑对话框:

// 编辑文本组件属性的对话框
void showTextEditDialog(int index) {
  TextWidgetData data = formItems[index]["data"];
  TextEditingController textController = TextEditingController(text: data.text);

  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text("编辑文本"),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          TextField(
            controller: textController,
            decoration: const InputDecoration(hintText: "输入文本内容"),
          ),
          const SizedBox(height: 16),
          Row(
            children: [
              const Text("加粗:"),
              Checkbox(
                value: data.isBold,
                onChanged: (value) {
                  setState(() {
                    data.isBold = value ?? false;
                  });
                },
              ),
            ],
          ),
        ],
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text("取消"),
        ),
        TextButton(
          onPressed: () {
            setState(() {
              data.text = textController.text;
            });
            Navigator.pop(context);
          },
          child: const Text("保存"),
        ),
      ],
    ),
  );
}

5. 重构列表构建逻辑,动态生成Widget

在ListView.builder中根据属性模型生成对应Widget:

ListView.builder(
  shrinkWrap: true,
  itemCount: formItems.length,
  itemBuilder: (context, index) {
    final item = formItems[index];
    switch (item["type"]) {
      case "Text":
        TextWidgetData data = item["data"];
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              PreDefinedFields.createText(
                text: data.text,
                style: TextStyle(fontWeight: data.isBold ? FontWeight.bold : FontWeight.normal),
              ),
              const Spacer(),
              IconButton(
                onPressed: () => showTextEditDialog(index),
                icon: const Icon(Icons.edit),
              ),
              IconButton(
                onPressed: () {
                  setState(() {
                    formItems.removeAt(index);
                  });
                },
                icon: const Icon(Icons.delete, color: Colors.red),
              ),
            ],
          ),
        );
      case "TextField":
        TextFieldWidgetData data = item["data"];
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              SizedBox(
                width: 400,
                child: PreDefinedFields.createTextField(
                  controller: data.controller,
                  decoration: InputDecoration(hintText: data.hintText),
                ),
              ),
              const Spacer(),
              IconButton(onPressed: () {}, icon: const Icon(Icons.edit)),
              IconButton(
                onPressed: () {
                  setState(() {
                    formItems.removeAt(index);
                  });
                },
                icon: const Icon(Icons.delete, color: Colors.red),
              ),
            ],
          ),
        );
      case "Button":
        ButtonWidgetData data = item["data"];
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              PreDefinedFields.createButton(
                text: data.text,
                onPressed: data.onPressed ?? () {
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(content: Text("按钮被点击")),
                  );
                },
              ),
              const Spacer(),
              IconButton(onPressed: () {}, icon: const Icon(Icons.edit)),
              IconButton(
                onPressed: () {
                  setState(() {
                    formItems.removeAt(index);
                  });
                },
                icon: const Icon(Icons.delete, color: Colors.red),
              ),
            ],
          ),
        );
      case "Date Picker":
        DatePickerWidgetData data = item["data"];
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              SizedBox(
                width: 400,
                child: TextField(
                  controller: data.controller,
                  decoration: InputDecoration(
                    icon: const Icon(Icons.calendar_today),
                    labelText: data.labelText,
                  ),
                  readOnly: true,
                  onTap: () async {
                    DateTime? pickedDate = await showDatePicker(
                      context: context,
                      initialDate: DateTime.now(),
                      firstDate: const DateTime(2000),
                      lastDate: const DateTime(2101),
                    );
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 11:56:45