如何实现无循环的顶部滑入式Flutter卡片切换动画?
实现顶部滑入的非循环卡片切换效果
方案一:基于flutter_swiper自定义布局与手势
flutter_swiper支持自定义布局配置,我们可以通过CustomLayoutOption调整卡片位置,再结合手势监听实现上下滑触发切换:
import 'package:flutter/material.dart'; import 'package:flutter_swiper/flutter_swiper.dart'; class TopSlideSwiper extends StatefulWidget { @override _TopSlideSwiperState createState() => _TopSlideSwiperState(); } class _TopSlideSwiperState extends State<TopSlideSwiper> { final SwiperController _controller = SwiperController(); final List<Widget> _pages = [ Container(color: Colors.blue), Container(color: Colors.black), Container(color: Colors.red), ]; @override Widget build(BuildContext context) { return Scaffold( body: GestureDetector( // 监听垂直滑动触发切换 onVerticalDragEnd: (details) { if (details.primaryVelocity! < 0 && _controller.index < _pages.length - 1) { _controller.next(); } else if (details.primaryVelocity! > 0 && _controller.index > 0) { _controller.previous(); } }, child: Swiper( loop: false, controller: _controller, itemCount: _pages.length, itemWidth: 400.0, itemHeight: 700.0, layout: SwiperLayout.CUSTOM, // 自定义卡片位置:上一张在顶部外,当前在原位,下一张在当前下方 customLayoutOption: CustomLayoutOption(startIndex: -1, stateCount: 3) .addRotate([0.0, 0.0, 0.0]) .addTranslate([ const Offset(0, -800), const Offset(0, 0), const Offset(0, 100), ]), itemBuilder: (context, index) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 40.0), child: _pages[index], ); }, duration: 500, curve: Curves.easeInOut, ), ), ); } }
方案二:完全自定义卡片滑动组件(更灵活)
如果flutter_swiper的自定义能力不够,直接用Stack+AnimationController实现顶部滑入逻辑:
import 'package:flutter/material.dart'; class TopSlideCardStack extends StatefulWidget { @override _TopSlideCardStackState createState() => _TopSlideCardStackState(); } class _TopSlideCardStackState extends State<TopSlideCardStack> with SingleTickerProviderStateMixin { late AnimationController _animationController; late Animation<Offset> _slideAnimation; int _currentIndex = 0; final List<Widget> _cards = [ Container(color: Colors.blue), Container(color: Colors.black), Container(color: Colors.red), ]; @override void initState() { super.initState(); _animationController = AnimationController( duration: const Duration(milliseconds: 500), vsync: this, ); _slideAnimation = Tween<Offset>( begin: const Offset(0, -1), // 初始位置在屏幕顶部外 end: Offset.zero, // 目标位置在屏幕中央 ).animate(CurvedAnimation( parent: _animationController, curve: Curves.easeInOut, )); _animationController.forward(); } void _nextCard() { if (_currentIndex < _cards.length - 1) { _animationController.reset(); setState(() => _currentIndex++); _animationController.forward(); } } void _prevCard() { if (_currentIndex > 0) { _animationController.reverse().then((_) { setState(() => _currentIndex--); _animationController.forward(); }); } } @override Widget build(BuildContext context) { return Scaffold( body: GestureDetector( onVerticalDragEnd: (details) { if (details.primaryVelocity! < 0) _nextCard(); else if (details.primaryVelocity! > 0) _prevCard(); }, child: Stack( alignment: Alignment.center, children: [ // 显示上一张卡片(如果存在) if (_currentIndex > 0) Padding( padding: const EdgeInsets.symmetric(horizontal: 40.0), child: _cards[_currentIndex - 1], ), // 当前卡片带顶部滑入动画 SlideTransition( position: _slideAnimation, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 40.0), child: _cards[_currentIndex], ), ), ], ), ), ); } @override void dispose() { _animationController.dispose(); super.dispose(); } }
内容的提问来源于stack exchange,提问作者ILii
相关产品推荐
相关产品推荐

