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

Flutter中多Widget同步滑动动画实现问题

实现Flutter文本轮播滑动动画效果

要实现你描述的依次滑动切换文本的动画,核心需要处理**文本入场(从右到中心)、停留、旧文本退场+新文本入场(同时进行)**这几个阶段的逻辑。你之前用AnimatedSwitcher的思路是对的,但嵌套SlideTransition和错误的Tween配置导致效果不符合预期,下面给出两种可行的实现方案:

方案一:使用AnimatedSwitcher(简洁易维护)

AnimatedSwitcher可以自动处理新旧Widget的切换动画,通过自定义transitionBuilder分别控制入场和退场动画,配合定时器实现循环切换:

import 'dart:async';
import 'package:flutter/material.dart';

class TextSlideCarousel extends StatefulWidget {
  const TextSlideCarousel({super.key});

  @override
  State<TextSlideCarousel> createState() => _TextSlideCarouselState();
}

class _TextSlideCarouselState extends State<TextSlideCarousel> {
  final List<String> _texts = ['one', 'two', 'three'];
  int _currentIndex = 0;
  Timer? _cycleTimer;

  @override
  void initState() {
    super.initState();
    // 初始文本停留1秒后开始循环切换
    Timer(const Duration(seconds: 1), _startCycle);
  }

  void _startCycle() {
    // 每2秒切换一次(1秒动画时长 + 1秒停留时长)
    _cycleTimer = Timer.periodic(const Duration(seconds: 2), (timer) {
      setState(() {
        _currentIndex = (_currentIndex + 1) % _texts.length;
      });
    });
  }

  @override
  void dispose() {
    _cycleTimer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: AnimatedSwitcher(
        duration: const Duration(milliseconds: 1000),
        switchInCurve: Curves.easeInOut,
        switchOutCurve: Curves.easeInOut,
        // 堆叠新旧文本,保证动画同时显示
        layoutBuilder: (currentChild, previousChildren) {
          return Stack(
            alignment: Alignment.center,
            children: [
              ...previousChildren,
              if (currentChild != null) currentChild,
            ],
          );
        },
        transitionBuilder: (Widget child, Animation<double> animation) {
          // 判断是新文本(入场)还是旧文本(退场),分别设置动画
          if (child.key == ValueKey(_texts[_currentIndex])) {
            // 新文本:从右侧滑到中心
            return SlideTransition(
              position: Tween<Offset>(
                begin: const Offset(1, 0),
                end: Offset.zero,
              ).animate(animation),
              child: child,
            );
          } else {
            // 旧文本:从中心滑到左侧
            return SlideTransition(
              position: Tween<Offset>(
                begin: Offset.zero,
                end: const Offset(-1, 0),
              ).animate(animation),
              child: child,
            );
          }
        },
        child: Text(
          _texts[_currentIndex],
          key: ValueKey(_texts[_currentIndex]), // 唯一key触发切换
          style: const TextStyle(fontSize: 30),
        ),
      ),
    );
  }
}

方案二:使用Stack+AnimationController(更灵活控制细节)

如果需要更精细的动画阶段控制,可直接用Stack叠加文本,通过AnimationController分别控制每个文本的位置:

import 'dart:async';
import 'package:flutter/material.dart';

class TextSlideCarousel extends StatefulWidget {
  const TextSlideCarousel({super.key});

  @override
  State<TextSlideCarousel> createState() => _TextSlideCarouselState();
}

class _TextSlideCarouselState extends State<TextSlideCarousel> with SingleTickerProviderStateMixin {
  final List<String> _texts = ['one', 'two', 'three'];
  int _currentIndex = 0;
  late AnimationController _slideController;
  late Animation<Offset> _currentTextOffset;
  late Animation<Offset> _nextTextOffset;
  bool _isTransitioning = false;

  @override
  void initState() {
    super.initState();
    _slideController = AnimationController(
      duration: const Duration(milliseconds: 1000),
      vsync: this,
    );

    // 初始动画:第一个文本从右侧滑到中心
    _currentTextOffset = Tween<Offset>(
      begin: const Offset(1, 0),
      end: Offset.zero,
    ).animate(CurvedAnimation(parent: _slideController, curve: Curves.easeInOut));

    _slideController.forward().then((_) {
      // 停留1秒后启动循环
      Timer(const Duration(seconds: 1), _startTransition);
    });
  }

  Future<void> _startTransition() async {
    setState(() => _isTransitioning = true);

    // 更新动画:当前文本滑向左侧,下一个文本从右侧滑入中心
    _currentTextOffset = Tween<Offset>(
      begin: Offset.zero,
      end: const Offset(-1, 0),
    ).animate(CurvedAnimation(parent: _slideController, curve: Curves.easeInOut));
    _nextTextOffset = Tween<Offset>(
      begin: const Offset(1, 0),
      end: Offset.zero,
    ).animate(CurvedAnimation(parent: _slideController, curve: Curves.easeInOut));

    await _slideController.forward(from: 0);

    // 更新当前索引,重置动画状态
    setState(() {
      _currentIndex = (_currentIndex + 1) % _texts.length;
      _isTransitioning = false;
      // 重置当前文本的位置到中心,准备下一次循环
      _currentTextOffset = Tween<Offset>(
        begin: Offset.zero,
        end: Offset.zero,
      ).animate(_slideController);
    });

    // 停留1秒后继续循环
    Timer(const Duration(seconds: 1), _startTransition);
  }

  @override
  void dispose() {
    _slideController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Stack(
        alignment: Alignment.center,
        children: [
          // 当前显示的文本
          SlideTransition(
            position: _currentTextOffset,
            child: Text(
              _texts[_currentIndex],
              style: const TextStyle(fontSize: 30),
            ),
          ),
          // 过渡时显示的下一个文本
          if (_isTransitioning)
            SlideTransition(
              position: _nextTextOffset,
              child: Text(
                _texts[(_currentIndex + 1) % _texts.length],
                style: const TextStyle(fontSize: 30),
              ),
            ),
        ],
      ),
    );
  }
}

你之前代码的问题分析

  1. 嵌套SlideTransition导致动画冲突:AnimatedSwitcher的transitionBuilder已经负责处理切换动画,无需再给子Widget套额外的SlideTransition。
  2. Tween配置错误:你设置的begin: Offset(3.0, 0.0)和end: Offset(1, 0)不是从右侧到中心的正确偏移(Offset(1,0)代表完全在右侧,Offset.zero才是中心)。
  3. 退场动画未正确定义:没有处理旧文本从中心滑向左侧的逻辑。

内容的提问来源于stack exchange,提问作者Aryan Sethi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 04:35:30