如何使用Flutter实现类似谷歌本地指南(Google Local Guide)的横向多对话框卡片(支持自动切换)
实现Flutter横向多对话框卡片(点击切换)
嘿,这个需求我刚好折腾过类似的,给你分享一套完整的实现方案,完全贴合你说的谷歌地图评价Dialog切换的效果👇
核心思路
- 用
PageView来管理横向的评价卡片,它天生支持页面切换动画,完美适配按钮触发切换的场景 - 把整个
PageView包裹在Flutter原生的Dialog里,通过自定义Dialog的大小让它更像卡片式弹窗 - 每个卡片底部设置触发按钮,点击后通过
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
相关产品推荐
相关产品推荐

