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), );
相关产品推荐
相关产品推荐

