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

如何实现无循环的顶部滑入式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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 18:24:04