Flutter透视PageView实现无限循环的问题求助
实现带透视效果的无限循环PageView问题
我实现了一个带透视效果的PageView,已适配新版Flutter且运行正常。现在需要让这个PageView实现无限循环效果,尝试过多种常规无限循环方案,但都会破坏透视效果。确认该功能可实现,求可行方案!
原代码:
Scaffold部分
class ProjectsViewState extends State<ProjectsView> with TickerProviderStateMixin { late PageViewHolder holder; late PageController _controller; double fraction = 0.50; @override void initState() { super.initState(); holder = PageViewHolder(value: 2.0); _controller = PageController(initialPage: 2, viewportFraction: fraction); _controller.addListener(() { holder.setValue(_controller.page); }); } int currentPage = 2; @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: ChangeNotifierProvider<PageViewHolder>.value( value: holder, child: Flexible( child: PageView.builder( controller: _controller, onPageChanged: (value) { setState(() { currentPage = value; }); }, itemCount: projectsCounter, physics: const BouncingScrollPhysics(), itemBuilder: (context, index) { return ProjectsGalleryView( number: index.toDouble(), fraction: fraction, ); }, ), ), ), ), ); } }
ProjectsGalleryView部分
class ProjectsGalleryView extends StatefulWidget { final double number; final double fraction; const ProjectsGalleryView( {super.key, required this.number, required this.fraction}); @override State<ProjectsGalleryView> createState() => _ProjectsGalleryViewState(); } class _ProjectsGalleryViewState extends State<ProjectsGalleryView> with SingleTickerProviderStateMixin { @override Widget build(BuildContext context) { double value = Provider.of<PageViewHolder>(context).value; double diff = (widget.number - value); //Matrix for Elements final Matrix4 pvMatrix = Matrix4.identity() ..setEntry(3, 3, 1 / 0.8) // Increasing Scale by 80% // ..setEntry(1, 1, widget.fraction) // Changing Scale Along Y Axis ..setEntry(3, 0, 0.003 * -diff); // Changing Perspective Along X Axis return Transform( transform: pvMatrix, alignment: FractionalOffset.center, child: Column( children: <Widget>[ Padding( padding: const EdgeInsets.symmetric(horizontal: 0), child: SizedBox( height: 200, width: 200, child: Container( decoration: BoxDecoration( color: Colors.redAccent, borderRadius: BorderRadius.circular(10), ), ), ), ), if (diff <= 1.0 && diff >= -1.0) ...[ AnimatedOpacity( duration: const Duration(milliseconds: 100), opacity: 1 - diff.abs(), child: const Padding( padding: EdgeInsets.symmetric( horizontal: 15, vertical: 20, ), child: Text( 'A Text with animated opacity', ), ), ), ] ], ), ); } }
解决方案
常规无限循环方案破坏透视的核心原因:透视效果依赖controller.page与页面索引的差值计算,而无限循环时controller.page会无限增大/减小,导致差值超出合理范围,打乱透视矩阵的计算逻辑。
以下是修正方案,核心是对page值和索引做循环归一化处理:
1. 修改ProjectsViewState
class ProjectsViewState extends State<ProjectsView> with TickerProviderStateMixin { late PageViewHolder holder; late PageController _controller; double fraction = 0.50; // 设足够大的初始页,避免快速滚动触达边界 final int initialPage = 1000; @override void initState() { super.initState(); holder = PageViewHolder(value: initialPage.toDouble()); _controller = PageController(initialPage: initialPage, viewportFraction: fraction); _controller.addListener(() { holder.setValue(_controller.page ?? initialPage.toDouble()); }); } @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: ChangeNotifierProvider<PageViewHolder>.value( value: holder, child: Flexible( child: PageView.builder( controller: _controller, // 设极大值模拟无限滚动 itemCount: 10000, physics: const BouncingScrollPhysics(), itemBuilder: (context, index) { // 取模得到真实页面索引,实现循环复用 final realIndex = index % projectsCounter; return ProjectsGalleryView( number: realIndex.toDouble(), fraction: fraction, totalCount: projectsCounter.toDouble(), ); }, ), ), ), ), ); } }
2. 修改ProjectsGalleryView
class ProjectsGalleryView extends StatefulWidget { final double number; final double fraction; // 新增总页数参数,用于差值归一化 final double totalCount; const ProjectsGalleryView({ super.key, required this.number, required this.fraction, required this.totalCount, }); @override State<ProjectsGalleryView> createState() => _ProjectsGalleryViewState(); } class _ProjectsGalleryViewState extends State<ProjectsGalleryView> with SingleTickerProviderStateMixin { @override Widget build(BuildContext context) { double value = Provider.of<PageViewHolder>(context).value; // 将无限增长的page值映射到[0, totalCount)区间 double normalizedValue = value % widget.totalCount; if (normalizedValue < 0) normalizedValue += widget.totalCount; // 调整差值为循环范围内的合理值,确保透视计算正确 double diff = widget.number - normalizedValue; if (diff > widget.totalCount / 2) { diff -= widget.totalCount; } else if (diff < -widget.totalCount / 2) { diff += widget.totalCount; } //Matrix for Elements final Matrix4 pvMatrix = Matrix4.identity() ..setEntry(3, 3, 1 / 0.8) ..setEntry(3, 0, 0.003 * -diff); return Transform( transform: pvMatrix, alignment: FractionalOffset.center, child: Column( children: <Widget>[ Padding( padding: const EdgeInsets.symmetric(horizontal: 0), child: SizedBox( height: 200, width: 200, child: Container( decoration: BoxDecoration( color: Colors.redAccent, borderRadius: BorderRadius.circular(10), ), ), ), ), if (diff <= 1.0 && diff >= -1.0) ...[ AnimatedOpacity( duration: const Duration(milliseconds: 100), opacity: 1 - diff.abs(), child: const Padding( padding: EdgeInsets.symmetric( horizontal: 15, vertical: 20, ), child: Text( 'A Text with animated opacity', ), ), ), ] ], ), ); } }
关键说明
- 初始页设为大数:避免用户快速滚动到边界,保证无限循环的流畅性。
- 索引取模:
index % projectsCounter让PageView循环复用真实页面内容。 - 差值归一化:将无限增长的
page值映射到真实页面区间,并调整差值为循环范围内的合理值,确保透视矩阵计算始终正确,不会因page值过大/过小导致透视错乱。
内容的提问来源于stack exchange,提问作者Chris
相关产品推荐
相关产品推荐

