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

Flutter新增Hero页面及设备管理功能实现求助

实现动态添加/删除Hero设备页面的完整方案

我来帮你梳理下完整的实现思路和代码改造步骤,原来的静态页面没法实现动态增删,咱们需要从状态管理、数据模型、页面复用这几个方面入手改造:

核心思路

  • 把静态的StatelessWidget改成StatefulWidget,用来维护设备列表的状态
  • 定义统一的设备数据模型,方便管理每个设备的信息(名称、图片、唯一标识)
  • 点击FAB弹出输入框收集设备名称,生成新设备并更新列表
  • 将固定的详情页改成通用页面,通过参数接收设备信息,实现页面复用
  • 详情页删除设备时,通过路由回调通知主页面移除对应设备

具体实现步骤

1. 定义设备数据模型

先创建一个Device类,存储每个设备的必要信息,确保Hero的tag唯一(用id作为tag):

class Device {
  final String id;
  final String name;
  final String imagePath;

  Device({
    required this.id,
    required this.name,
    required this.imagePath,
  });
}

2. 改造DevicesPageHero为StatefulWidget

原来的静态布局改成动态渲染设备列表,同时实现添加设备的弹窗逻辑:

// 先定义全局样式(如果之前没有的话)
const color_1 = Colors.blue;
const color_3 = Colors.green;
const mytextStyle = TextStyle(fontSize: 16, fontWeight: FontWeight.w500);

class DevicesPageHero extends StatefulWidget {
  @override
  State<DevicesPageHero> createState() => _DevicesPageHeroState();
}

class _DevicesPageHeroState extends State<DevicesPageHero> {
  // 设备列表,初始化两个示例设备
  final List<Device> _devices = [
    Device(
      id: 'matrix1',
      name: 'Matrix Kitchen',
      imagePath: 'imgs/matrix1.png',
    ),
    Device(
      id: 'matrix2',
      name: 'PARTY ROOM',
      imagePath: 'imgs/matrix2.png',
    ),
  ];

  // 弹出添加设备的输入框
  void _showAddDeviceDialog() {
    final TextEditingController _nameController = TextEditingController();
    showDialog(
      context: context,
      builder: (ctx) => AlertDialog(
        title: const Text('添加新设备'),
        content: TextField(
          controller: _nameController,
          decoration: const InputDecoration(hintText: '输入设备名称'),
          autofocus: true,
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(ctx),
            child: const Text('取消'),
          ),
          TextButton(
            onPressed: () {
              if (_nameController.text.trim().isNotEmpty) {
                // 生成唯一id(这里用时间戳简单实现)
                final newDevice = Device(
                  id: DateTime.now().millisecondsSinceEpoch.toString(),
                  name: _nameController.text.trim(),
                  imagePath: 'imgs/matrix1.png', // 可以换成默认图片,或者后续扩展让用户选择
                );
                setState(() {
                  _devices.add(newDevice);
                });
                Navigator.pop(ctx);
              }
            },
            child: const Text('确认'),
          ),
        ],
      ),
    );
  }

  // 删除设备的回调方法
  void _deleteDevice(String deviceId) {
    setState(() {
      _devices.removeWhere((device) => device.id == deviceId);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: color_1,
        title: const Text('Devices'),
      ),
      // 用Wrap实现自动换行,适配多个设备
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Wrap(
          spacing: 20,
          runSpacing: 20,
          children: _devices.map((device) {
            return Hero(
              tag: device.id,
              child: GestureDetector(
                onTap: () async {
                  // 跳转详情页,并等待删除回调
                  final shouldDelete = await Navigator.push(
                    context,
                    MaterialPageRoute(
                      builder: (context) => DeviceDetailPage(
                        device: device,
                      ),
                    ),
                  );
                  if (shouldDelete == true) {
                    _deleteDevice(device.id);
                  }
                },
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Image.asset(
                      device.imagePath,
                      height: 100,
                      width: 100,
                    ),
                    const SizedBox(height: 8),
                    Text(device.name, style: mytextStyle),
                  ],
                ),
              ),
            );
          }).toList(),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _showAddDeviceDialog,
        child: const Icon(Icons.add),
        backgroundColor: color_3,
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
    );
  }
}

3. 实现通用的DeviceDetailPage

把原来的固定详情页改成通用页面,接收Device参数,同时实现删除确认逻辑:

class DeviceDetailPage extends StatelessWidget {
  final Device device;

  const DeviceDetailPage({super.key, required this.device});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: color_1,
        title: Text(device.name),
      ),
      body: Column(
        children: [
          Hero(
            tag: device.id,
            child: Image.asset(
              device.imagePath,
              height: 150,
              width: 150,
            ),
          ),
          const SizedBox(height: 16),
          Container(
            width: MediaQuery.of(context).size.width,
            padding: const EdgeInsets.symmetric(horizontal: 16),
            child: Text(
              "Live update ${device.name}",
              style: const TextStyle(
                fontSize: 15,
                fontFamily: "Arial",
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
          const Divider(),
          const Spacer(flex: 1),
          Container(
            width: MediaQuery.of(context).size.width,
            padding: const EdgeInsets.symmetric(horizontal: 16),
            child: const Text("Option 1 ...", textAlign: TextAlign.left),
          ),
          const Divider(),
          const Spacer(flex: 1),
          Container(
            width: MediaQuery.of(context).size.width,
            padding: const EdgeInsets.symmetric(horizontal: 16),
            child: const Text("Option 2 ...", textAlign: TextAlign.left),
          ),
          const Divider(),
          const Spacer(flex: 80),
          IconButton(
            icon: const Icon(Icons.delete, color: Colors.red),
            onPressed: () {
              showDialog(
                context: context,
                builder: (ctx) => AlertDialog(
                  title: const Text('确认删除'),
                  content: Text('确定要删除${device.name}吗?'),
                  actions: [
                    TextButton(
                      onPressed: () => Navigator.pop(ctx),
                      child: const Text('取消'),
                    ),
                    TextButton(
                      onPressed: () {
                        // 返回true告诉主页面要删除该设备
                        Navigator.pop(ctx);
                        Navigator.pop(context, true);
                      },
                      child: const Text('删除'),
                    ),
                  ],
                ),
              );
            },
          ),
          const SizedBox(height: 16),
        ],
      ),
    );
  }
}

关键细节说明

  • Hero标签唯一性:用设备的id作为Hero的tag,避免多个设备tag冲突导致动画异常
  • 状态管理:通过setState更新设备列表,触发UI重绘
  • 页面复用:通用详情页支持所有设备,不用为每个设备单独写页面
  • 输入校验:添加设备时判断输入是否为空,避免空名称的设备
  • 删除确认:增加二次确认弹窗,防止误操作

内容的提问来源于stack exchange,提问作者spawn.World1

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:26:33