Flutter Motion Kit

可预览的 Flutter 动画 + 避坑指南 · 支持 Claude Code 一键复用

← 返回

自定义页面转场(淡入+上滑)

分类 explicit · 难度 2/5 · 验证于 Flutter 3.32 / 2026-06

用 PageRouteBuilder 自定义进场动画(淡入 + 轻微上滑),替代默认平台转场。

⏳ 在线预览未就绪:运行 node scripts/sync-gists.mjs 生成 gist 后即可内嵌。

代码

// ✅ 推荐:PageRouteBuilder + CurvedAnimation,淡入 + 上滑,进/出时长都显式设置。
// 可直接粘进 DartPad (https://dartpad.dev) 运行。
import 'package:flutter/material.dart';

void main() => runApp(const _App());

class _App extends StatelessWidget {
  const _App();
  @override
  Widget build(BuildContext context) => const MaterialApp(
        debugShowCheckedModeBanner: false,
        home: _HomePage(),
      );
}

// 可复用的转场:淡入 + 轻微上滑
Route<T> fadeSlideRoute<T>(Widget page) {
  return PageRouteBuilder<T>(
    transitionDuration: const Duration(milliseconds: 350),
    reverseTransitionDuration: const Duration(milliseconds: 300),
    pageBuilder: (context, animation, secondaryAnimation) => page,
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      final curved = CurvedAnimation(parent: animation, curve: Curves.easeOutCubic);
      return FadeTransition(
        opacity: curved,
        child: SlideTransition(
          position: Tween<Offset>(
            begin: const Offset(0, 0.08),
            end: Offset.zero,
          ).animate(curved),
          child: child, // 复用传入的 child
        ),
      );
    },
  );
}

class _HomePage extends StatelessWidget {
  const _HomePage();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home')),
      body: Center(
        child: FilledButton(
          onPressed: () => Navigator.of(context).push(fadeSlideRoute(const _SecondPage())),
          child: const Text('Open with fade + slide'),
        ),
      ),
    );
  }
}

class _SecondPage extends StatelessWidget {
  const _SecondPage();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Second')),
      body: const Center(child: Text('Hello 👋')),
    );
  }
}

⚠️ 坑(4)

直接用线性 animation 做 Tween,过渡很生硬。
✅ 用 CurvedAnimation 包一层(如 easeOutCubic)再驱动 Fade/Slide。
official-docs · 出处
只设 transitionDuration、忘了 reverseTransitionDuration,返回过渡时长不一致。
✅ 两个时长都显式设置。
official-docs · 出处
在 transitionsBuilder 里重建大子树,而非使用传入的 child,会每帧重建页面。
✅ 始终复用回调里的 child 参数。
official-docs · 出处
想全 App 统一转场却在每处手写,难维护且不一致。
✅ 用 ThemeData.pageTransitionsTheme 配 PageTransitionsBuilder 统一覆盖。
official-docs · 出处

官方文档