如何在Flutter中创建类似塔罗牌的环形卡片轮UI
Flutter环形卡片轮UI实现方案
核心思路
要实现这种环形滚动的卡片轮,关键是让滚动时卡片根据位置动态调整缩放比例、透明度和Y轴偏移,模拟环形的视觉弧度。ListWheelScrollView默认是线性滚轮,很难实现这种环形效果,推荐以下两种实现方式:
方案一:纯原生自定义滚动布局
通过GestureDetector监听滚动手势,结合AnimatedBuilder和Transform控制每个卡片的视觉变换,核心逻辑是根据卡片索引与当前滚动偏移量的关系,计算每个卡片的动态参数。
代码示例
import 'package:flutter/material.dart'; class CircularCardWheel extends StatefulWidget { final List<Widget> cards; final double cardHeight; final double cardWidth; const CircularCardWheel({ super.key, required this.cards, this.cardHeight = 200, this.cardWidth = 150, }); @override State<CircularCardWheel> createState() => _CircularCardWheelState(); } class _CircularCardWheelState extends State<CircularCardWheel> { final _scrollController = ScrollController(); double _currentOffset = 0.0; @override void initState() { super.initState(); _scrollController.addListener(() { setState(() => _currentOffset = _scrollController.offset); }); } @override void dispose() { _scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; return SizedBox( height: widget.cardHeight * 1.5, child: ListView.builder( controller: _scrollController, scrollDirection: Axis.horizontal, itemCount: widget.cards.length, padding: EdgeInsets.symmetric(horizontal: screenWidth / 2 - widget.cardWidth / 2), itemBuilder: (context, index) { // 计算卡片中心与屏幕中心的距离 final cardCenterOffset = index * widget.cardWidth + widget.cardWidth / 2; final distanceFromCenter = (cardCenterOffset - _currentOffset - screenWidth / 2).abs(); // 根据距离动态调整视觉参数 final scale = 1 - (distanceFromCenter / (screenWidth / 2)) * 0.3; final opacity = 1 - (distanceFromCenter / (screenWidth / 2)) * 0.5; final yOffset = -distanceFromCenter * 0.1; return Transform( transform: Matrix4.identity() ..translate(0, yOffset) ..scale(scale), alignment: Alignment.center, child: Opacity( opacity: opacity.clamp(0.5, 1.0), child: SizedBox( height: widget.cardHeight, width: widget.cardWidth, child: widget.cards[index], ), ), ); }, ), ); } } // 使用示例 class DemoPage extends StatelessWidget { const DemoPage({super.key}); @override Widget build(BuildContext context) { final cards = List.generate( 10, (index) => Card( elevation: 8, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: Center(child: Text('卡片 $index', style: const TextStyle(fontSize: 20))), ), ); return Scaffold( appBar: AppBar(title: const Text('环形卡片轮')), body: Center(child: CircularCardWheel(cards: cards)), ); } }
说明
- 通过滚动偏移量计算卡片与屏幕中心的距离,动态调整缩放、透明度和Y轴偏移,模拟环形弧度
- 横向ListView配合左右padding,让卡片滚动时始终以屏幕中心为视觉焦点
- 可根据需求调整
scale、opacity、yOffset的系数,优化环形效果的强弱
方案二:使用第三方包快速实现
如果不想手动处理复杂的变换逻辑,可以借助成熟的第三方包:
carousel_slider:支持自定义Transformer,快速实现卡片的环形排列flutter_carousel_widget:提供预设的环形滚动配置
以carousel_slider为例,自定义环形Transformer:
import 'package:carousel_slider/carousel_slider.dart'; import 'package:flutter/material.dart'; class CircularTransformer extends CarouselTransformer { @override Widget transformItem(Widget child, int index, int pageIndex) { final offset = (index - pageIndex).abs(); final scale = 1 - offset * 0.2; final opacity = 1 - offset * 0.3; final yOffset = -offset * 20; return Transform( transform: Matrix4.identity() ..translate(0, yOffset) ..scale(scale), alignment: Alignment.center, child: Opacity(opacity: opacity.clamp(0.5, 1.0), child: child), ); } } // 使用示例 class CarouselDemo extends StatelessWidget { const CarouselDemo({super.key}); @override Widget build(BuildContext context) { final items = List.generate(10, (index) => Card( elevation: 8, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: Center(child: Text('卡片 $index', style: const TextStyle(fontSize: 20))), )); return Scaffold( appBar: AppBar(title: const Text('环形卡片轮')), body: Center( child: CarouselSlider( items: items, options: CarouselOptions( height: 300, viewportFraction: 0.6, enableInfiniteScroll: true, transform: CircularTransformer(), ), ), ), ); } }
优化建议
- 给滚动控制器添加阻尼效果,让滚动更顺滑
- 给中间选中的卡片增加额外阴影或边框,强化选中状态
- 修改轴方向和计算逻辑,可实现垂直方向的环形滚动
内容的提问来源于stack exchange,提问作者DholaSain
相关产品推荐
相关产品推荐

