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

如何在Flutter中实现Figma的Smart Animate过渡效果?

在Flutter中实现类似Figma Smart Animate的引导页平滑过渡效果

要实现Figma Smart Animate那种元素级的平滑过渡(而非普通页面堆叠导航),核心思路是将所有引导页元素放在同一个页面的层级中,通过滚动偏移控制元素的动画参数,而不是使用独立页面的Navigator导航。以下是具体实现方案:

方案一:PageView + 自定义元素动画控制

这种方式通过监听PageView的滚动进度,实时调整各个引导页元素的位置、透明度和缩放,模拟Smart Animate的连贯过渡效果。

步骤1:定义引导页数据模型

class OnboardingItem {
  final String title;
  final String description;
  final String iconAsset;

  OnboardingItem({
    required this.title,
    required this.description,
    required this.iconAsset,
  });
}

步骤2:实现带动画的引导页页面

import 'package:flutter/material.dart';

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

  @override
  State<SmartAnimateOnboarding> createState() => _SmartAnimateOnboardingState();
}

class _SmartAnimateOnboardingState extends State<SmartAnimateOnboarding> {
  final PageController _pageController = PageController();
  double _currentPage = 0.0;

  final List<OnboardingItem> _onboardingItems = [
    OnboardingItem(
      title: "高效管理",
      description: "一键整理你的日常任务,让效率翻倍",
      iconAsset: "assets/ic_task.png",
    ),
    OnboardingItem(
      title: "智能提醒",
      description: "基于你的习惯推送精准提醒,绝不遗漏重要事项",
      iconAsset: "assets/ic_remind.png",
    ),
    OnboardingItem(
      title: "数据同步",
      description: "多设备实时同步,随时随地访问你的任务数据",
      iconAsset: "assets/ic_sync.png",
    ),
  ];

  @override
  void initState() {
    super.initState();
    // 监听PageView滚动,更新当前页面偏移
    _pageController.addListener(() {
      setState(() => _currentPage = _pageController.page!);
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          // 空的PageView用于控制滚动逻辑
          PageView.builder(
            controller: _pageController,
            itemCount: _onboardingItems.length,
            itemBuilder: (context, index) => const SizedBox.shrink(),
          ),
          // 动画元素层:根据滚动偏移控制元素过渡
          AnimatedBuilder(
            animation: _pageController,
            builder: (context, child) {
              int currentIndex = _currentPage.floor();
              double offset = _currentPage - currentIndex;

              // 处理向右滑动的情况(offset为负时调整索引)
              if (offset < 0) {
                currentIndex -= 1;
                offset += 1;
              }

              return Stack(
                children: [
                  // 上一页元素(向右滑动时显示过渡动画)
                  if (currentIndex > 0)
                    _buildPageContent(_onboardingItems[currentIndex - 1], 1 - offset),
                  // 当前页面元素
                  _buildPageContent(_onboardingItems[currentIndex], offset),
                  // 下一页元素(向左滑动时显示过渡动画)
                  if (currentIndex < _onboardingItems.length - 1)
                    _buildPageContent(_onboardingItems[currentIndex + 1], offset - 1),
                ],
              );
            },
          ),
          // 底部导航控件
          Positioned(
            bottom: 40,
            left: 20,
            right: 20,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                TextButton(
                  onPressed: _currentPage > 0
                      ? () => _pageController.previousPage(
                            duration: const Duration(milliseconds: 500),
                            curve: Curves.easeInOut,
                          )
                      : null,
                  child: const Text("上一页"),
                ),
                // 页面指示器
                Row(
                  children: List.generate(_onboardingItems.length, (index) {
                    final isActive = index == _currentPage.floor();
                    return Container(
                      width: isActive ? 16 : 12,
                      height: 12,
                      margin: const EdgeInsets.symmetric(horizontal: 4),
                      decoration: BoxDecoration(
                        shape: BoxShape.circle,
                        color: isActive ? Colors.blue : Colors.grey.shade300,
                      ),
                    );
                  }),
                ),
                TextButton(
                  onPressed: _currentPage < _onboardingItems.length - 1
                      ? () => _pageController.nextPage(
                            duration: const Duration(milliseconds: 500),
                            curve: Curves.easeInOut,
                          )
                      : () => Navigator.pushReplacementNamed(context, "/home"),
                  child: Text(_currentPage < 2 ? "下一页" : "完成"),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  // 构建单个引导页的内容,根据动画值调整过渡效果
  Widget _buildPageContent(OnboardingItem item, double animationValue) {
    return Opacity(
      opacity: 1 - animationValue.abs(),
      child: Transform(
        transform: Matrix4.translationValues(
          animationValue * MediaQuery.of(context).size.width,
          0,
          0,
        )..scale(1 - animationValue.abs() * 0.1),
        alignment: Alignment.center,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Image.asset(
              item.iconAsset,
              width: 150,
              height: 150,
            ),
            const SizedBox(height: 30),
            Text(
              item.title,
              style: const TextStyle(
                fontSize: 24,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 16),
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 40),
              child: Text(
                item.description,
                textAlign: TextAlign.center,
                style: const TextStyle(fontSize: 16, color: Colors.grey),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

关键原理说明

  1. 摒弃独立页面导航:普通的Navigator.push是堆叠页面,无法实现元素级的平滑过渡,因此我们将所有引导页内容放在同一个页面的Stack中。
  2. 滚动驱动动画:通过PageController监听滚动偏移,将偏移值转化为元素的动画参数(平移距离、透明度、缩放比例),实现类似Figma Smart Animate的连贯过渡。
  3. 多元素协同动画:通过AnimatedBuilder统一控制所有元素的动画进度,确保切换时各个元素的过渡同步且自然。

如果你需要更精细的元素动画(比如不同元素使用不同的动画曲线或延迟),可以在_buildPageContent中为每个元素单独设置动画参数,进一步贴近Figma的效果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 21:40:19