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

如何使用Flutter实现类似谷歌本地指南(Google Local Guide)的横向多对话框卡片(支持自动切换)

实现Flutter横向多对话框卡片(点击切换)

嘿,这个需求我刚好折腾过类似的,给你分享一套完整的实现方案,完全贴合你说的谷歌地图评价Dialog切换的效果👇

核心思路

  1. 用PageView来管理横向的评价卡片,它天生支持页面切换动画,完美适配按钮触发切换的场景
  2. 把整个PageView包裹在Flutter原生的Dialog里,通过自定义Dialog的大小让它更像卡片式弹窗
  3. 每个卡片底部设置触发按钮,点击后通过PageController切换到下一页,最后一页点击后关闭Dialog并处理提交逻辑

完整代码实现

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text("评价卡片示例")),
        body: Center(
          child: ElevatedButton(
            onPressed: () => showRatingDialog(context),
            child: const Text("打开评价弹窗"),
          ),
        ),
      ),
    );
  }

  void showRatingDialog(BuildContext context) {
    // 模拟评价数据,你可以替换成自己的业务内容
    final ratingItems = [
      {"title": "评价店铺环境", "hint": "请描述您对环境的感受"},
      {"title": "评价服务态度", "hint": "请描述您对服务的感受"},
      {"title": "评价菜品口味", "hint": "请描述您对口味的感受"},
    ];

    showDialog(
      context: context,
      builder: (context) {
        return RatingDialog(items: ratingItems);
      },
    );
  }
}

class RatingDialog extends StatefulWidget {
  final List<Map<String, String>> items;
  const RatingDialog({super.key, required this.items});

  @override
  State<RatingDialog> createState() => _RatingDialogState();
}

class _RatingDialogState extends State<RatingDialog> {
  late PageController _pageController;
  int _currentPage = 0;

  @override
  void initState() {
    super.initState();
    _pageController = PageController(initialPage: 0);
  }

  @override
  void dispose() {
    _pageController.dispose();
    super.dispose();
  }

  void _handleNext() {
    if (_currentPage < widget.items.length - 1) {
      // 还有下一页,执行切换动画
      _pageController.nextPage(
        duration: const Duration(milliseconds: 300),
        curve: Curves.easeInOut,
      );
      setState(() => _currentPage++);
    } else {
      // 最后一页,关闭Dialog并触发提交逻辑
      Navigator.pop(context);
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text("评价提交成功!")),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Dialog(
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
      child: Container(
        width: MediaQuery.of(context).size.width * 0.85,
        padding: const EdgeInsets.all(20),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            // 顶部进度指示器,让用户直观看到当前进度
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: List.generate(widget.items.length, (index) {
                return Container(
                  width: 8,
                  height: 8,
                  margin: const EdgeInsets.symmetric(horizontal: 4),
                  decoration: BoxDecoration(
                    color: _currentPage == index ? Colors.blue : Colors.grey.shade300,
                    borderRadius: BorderRadius.circular(4),
                  ),
                );
              }),
            ),
            const SizedBox(height: 20),
            // 横向卡片展示区域
            SizedBox(
              height: 220,
              child: PageView.builder(
                controller: _pageController,
                physics: const NeverScrollableScrollPhysics(), // 禁止手动滑动,只允许按钮切换
                itemCount: widget.items.length,
                itemBuilder: (context, index) {
                  final item = widget.items[index];
                  return Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        item["title"]!,
                        style: const TextStyle(
                          fontSize: 20,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(height: 16),
                      TextField(
                        decoration: InputDecoration(
                          hintText: item["hint"],
                          border: OutlineInputBorder(
                            borderRadius: BorderRadius.circular(8),
                          ),
                        ),
                        maxLines: 4,
                      ),
                    ],
                  );
                },
              ),
            ),
            const SizedBox(height: 20),
            // 底部切换按钮,文本随进度自动变化
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: _handleNext,
                style: ElevatedButton.styleFrom(
                  padding: const EdgeInsets.symmetric(vertical: 14),
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(8),
                  ),
                ),
                child: Text(
                  _currentPage == widget.items.length - 1 ? "提交评价" : "下一步",
                  style: const TextStyle(fontSize: 16),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

关键细节说明

  • 禁止手动滑动:设置PageView的physics为NeverScrollableScrollPhysics(),确保用户只能通过按钮切换页面,完全匹配谷歌地图的交互逻辑
  • 进度指示器:顶部的小圆点可以让用户直观看到当前是第几个评价项,提升整体体验
  • Dialog样式自定义:通过shape和Container设置Dialog的圆角和宽高,让它更接近谷歌地图的卡片式弹窗风格
  • 动态按钮文本:根据当前页面自动切换按钮文本为“下一步”或“提交评价”,符合用户操作预期

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 18:47:30