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

Flutter下拉菜单即时切换+加载数据时显示进度指示器方案

问题描述

我正在构建一个展示与当前选中分类匹配条目的UI,核心功能已实现,但希望在获取新选中分类的条目时显示Progress Indicator。当前UI需等待数据获取完成后才切换选中项,显得不够流畅。请问如何修改代码,让下拉菜单的选中项即时切换,加载匹配条目时显示进度指示器,数据就绪后再显示条目字段?

解决方案

调整状态管理逻辑,分离下拉选中状态与数据加载状态,实现即时切换+加载提示:

  • 添加加载状态标记:新增isLoading布尔变量,用于控制进度指示器的显示与隐藏
  • 即时更新下拉选中值:在下拉选项变更时立即更新dropdownValue并刷新UI,再触发数据加载
  • 异步加载数据并更新状态:加载数据前标记为加载中,加载完成后更新设备列表并取消加载标记
  • 根据状态渲染对应UI:加载中显示进度指示器,加载完成后根据设备列表内容显示条目或空提示

修改后的完整代码

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

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const SignUpApp());
}

class SignUpApp extends StatelessWidget {
  const SignUpApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      routes: {
        '/': (context) => BrowseScreen(),
        '/welcome': (context) => const WelcomeScreen(),
        '/browse': (context) => BrowseScreen(),
      },
    );
  }
}

class BrowseScreen extends StatefulWidget {
  BrowseScreen({super.key});

  @override
  State<BrowseScreen> createState() => _BrowseScreenState();
}

class _BrowseScreenState extends State<BrowseScreen> {
  late final Future<List<EquipmentCategory>> categories;
  late List<Equipment> equipment = [];
  String dropdownValue = '';
  bool isLoading = false; // 新增加载状态变量

  //For local testing without Firebase
  final List<EquipmentCategory> equipmentCategories = [
        EquipmentCategory('1iyQmcbWIV6gNMk5PX9E', 'Lawn & Garden - motorised'),
        EquipmentCategory('ofYNwqKtvbctjiwETKOZ', 'Trailers'),
        EquipmentCategory('r5FSAIyIrinkr8m1TXTj', 'Lawn & Garden - manual'),
      ];

  final List<Equipment> trailerEquipmentList = [Equipment(id: 'd9A8qfHT8lKIzhUk55K5', name: 'Two-wheel trailer 900mm', categories: ['ofYNwqKtvbctjiwETKOZ'], photos:    ["https://media.prod.bunnings.com.au/api/public/content/c65c035a7c7b4a7282c3b5f3def5ec76?v=94a4edf7&t=w500dpr1"]) ];

  late final Map<String, List<Equipment>> equipmentByCategory = {
    'ofYNwqKtvbctjiwETKOZ': trailerEquipmentList,
    '1iyQmcbWIV6gNMk5PX9E': [],
    'r5FSAIyIrinkr8m1TXTj': []
  };

  @override
  void initState() {
    super.initState();
    categories = _fetchEquipmentCategories();
    dropdownValue = '1iyQmcbWIV6gNMk5PX9E';
    // 初始化加载默认分类数据
    _fetchEquipment(dropdownValue).then((fetchedEquipment) {
      setState(() {
        equipment = fetchedEquipment ?? [];
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Material(
        child: FutureBuilder(
            future: categories,
            builder: (BuildContext context, AsyncSnapshot snapshot) {
              if (!snapshot.hasData) {
                // 分类数据加载中
                return Center(
                  child: CircularProgressIndicator(
                      backgroundColor: Colors.red,
                      valueColor:
                          new AlwaysStoppedAnimation<Color>(Colors.blue)),
                );
              } else {
                return Container(
                    color: CupertinoColors.white,
                    padding: const EdgeInsets.only(top: 20),
                    child: Column(
                        mainAxisAlignment: MainAxisAlignment.start,
                        children: [
                          Column(children: [
                            DropdownButton<String>(
                              icon: const Icon(Icons.arrow_downward),
                              elevation: 16,
                              style:
                                  const TextStyle(color: Colors.deepPurple),
                              underline: Container(
                                height: 2,
                                color: Colors.deepPurpleAccent,
                              ),
                              onChanged: (String? value) {
                                if (value == null) return;
                                // 立即更新选中值并触发UI刷新
                                setState(() {
                                  dropdownValue = value;
                                  isLoading = true;
                                });
                                // 异步加载对应分类数据
                                _fetchEquipment(value).then((fetchedEquipment) {
                                  setState(() {
                                    equipment = fetchedEquipment ?? [];
                                    isLoading = false;
                                  });
                                });
                              },
                              items: snapshot.data
                                  .map<DropdownMenuItem<String>>(
                                      (EquipmentCategory value) {
                                return DropdownMenuItem<String>(
                                  value: value.id,
                                  child: Text(value.name),
                                );
                              }).toList(),
                              value: dropdownValue,
                            ),
                            const SizedBox(height: 20),
                            // 根据状态显示不同内容
                            if (isLoading)
                              const Center(child: CircularProgressIndicator())
                            else if (equipment.isNotEmpty)
                              Column(
                                children: equipment.map((item) => Padding(
                                  padding: const EdgeInsets.symmetric(vertical: 8.0),
                                  child: Text(item.name, style: const TextStyle(color: Colors.black, fontSize: 16)),
                                )).toList(),
                              )
                            else
                              const Text('EMPTY', style: TextStyle(fontSize: 16))
                          ])
                        ]));
              }
            }));
  }

  Future<List<EquipmentCategory>> _fetchEquipmentCategories() async {
    return Future.delayed(
      const Duration(seconds: 1),
      () => equipmentCategories,
    );
  }

  // 修改为接收分类ID参数
  Future<List<Equipment>?> _fetchEquipment(String categoryId) async {
    await Future.delayed(const Duration(seconds: 1));
    return equipmentByCategory[categoryId];
  }
}

class EquipmentCategory {
  String id;
  String name;

  EquipmentCategory(this.id, this.name);
}

class Equipment {
  late String id;
  late String name;
  late List<String> categories;
  late List<String> photos;

  Equipment({required id, required name, required categories, required photos});

  factory Equipment.fromJson(Map<String, dynamic> json) {
    return Equipment(
        id: json['id'],
        name: json['name'],
        categories: json['categories'],
        photos: json['photos']);
  }
}

class _WelcomeFormState extends State<WelcomeScreen> {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: CupertinoColors.white,
      padding: const EdgeInsets.only(top: 20),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          MyButton(
            text: 'Browse',
            icon: CupertinoIcons.search,
            callback: _showAvailableEquipmentScreen,
          ),
          MyButton(
            text: 'Return',
            icon: Icons.recycling,
            callback: () {},
          ),
          MyButton(
            text: 'Login',
            icon: Icons.login,
            callback: () {
              showCupertinoDialog(
                context: context,
                builder: (context) => CupertinoAlertDialog(
                  title: const Text('Hello, World!'),
                  actions: [
                    CupertinoDialogAction(
                      child: const Text('Close'),
                      onPressed: () {
                        Navigator.pop(context);
                      },
                    ),
                  ],
                ),
              );
            },
          ),
        ],
      ),
    );
  }

  void _showAvailableEquipmentScreen() {
    Navigator.of(context).pushNamed('/browse');
  }
}

class WelcomeScreen extends StatefulWidget {
  const WelcomeScreen({super.key});

  @override
  State<WelcomeScreen> createState() => _WelcomeFormState();
}

class MyButton extends StatelessWidget {
  const MyButton({
    super.key,
    required this.text,
    required this.icon,
    required this.callback,
  });

  final String text;
  final IconData icon;
  final VoidCallback callback;

  final double buttonSize = 90;

  @override
  Widget build(BuildContext context) {
    return CupertinoButton(
      padding: EdgeInsets.zero,
      minSize: 0,
      onPressed: callback,
      child: Container(
        decoration: BoxDecoration(
          color: const Color(0xFFEAEAEA),
          borderRadius: BorderRadius.circular(15),
        ),
        padding: const EdgeInsets.symmetric(
          vertical: 20,
          horizontal: 30,
        ),
        child: Column(
          children: [
            Stack(
              children: [
                Container(
                  width: buttonSize,
                  height: buttonSize,
                  decoration: BoxDecoration(
                    color: CupertinoColors.black,
                    borderRadius: BorderRadius.circular(17),
                  ),
                ),
                Container(
                  width: buttonSize,
                  height: buttonSize,
                  decoration: BoxDecoration(
                    color: CupertinoColors.white,
                    borderRadius: BorderRadius.circular(20),
                  ),
                  child: Icon(
                    icon,
                    size: 60,
                    color: CupertinoColors.black,
                  ),
                ),
              ],
            ),
            const SizedBox(height: 10),
            Text(
              text,
              style: const TextStyle(
                fontWeight: FontWeight.w600,
                color: CupertinoColors.black,
              ),
            )
          ],
        ),
      ),
    );
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 20:29:58