likes
comments
collection
share

Flutter 动画 路由切换

作者站长头像
站长
· 阅读数 4

CupertinoPageRoute

CupertinoPageRoute是Cupertino组件库提供的iOS风格的路由切换组件,它实现的就是左右滑动切换。

Flutter 动画 路由切换

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: GestureDetector(
          onTap: () {
            Navigator.push(
                context,
                CupertinoPageRoute(
                  builder: (context) => const MyTwoPage(title: "下一个页面"),
                ));
          },
          child: const Text('跳转下一个页面'),
        ),
      ),
    );
  }
}

class MyTwoPage extends StatefulWidget {
  const MyTwoPage({super.key, required this.title});

  final String title;

  @override
  State<MyTwoPage> createState() => _MyTwoPageState();
}

class _MyTwoPageState extends State<MyTwoPage> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: GestureDetector(
          onTap: () {},
          child: const Text('返回上一个页面'),
        ),
      ),
    );
  }
}

PageRouteBuilder

自定义路由切换动画呢?答案就是PageRouteBuilder。下面我们来看看如何使用PageRouteBuilder来自定义路由切换动画。例如我们想以渐隐渐入动画来实现路由过渡,实现代码如下:

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: GestureDetector(
          onTap: () {
            Navigator.push(
              context,
              PageRouteBuilder(
                transitionDuration:
                    const Duration(milliseconds: 500), //动画时间为500毫秒
                pageBuilder: (BuildContext context, Animation<double> animation,
                    Animation secondaryAnimation) {
                  return FadeTransition(
                    //使用渐隐渐入过渡,
                    opacity: animation,
                    child: const MyTwoPage(title: "下一个页面"), //路由B
                  );
                },
              ),
            );
          },
          child: const Text('跳转下一个页面'),
        ),
      ),
    );
  }
}

class MyTwoPage extends StatefulWidget {
  const MyTwoPage({super.key, required this.title});

  final String title;

  @override
  State<MyTwoPage> createState() => _MyTwoPageState();
}

class _MyTwoPageState extends State<MyTwoPage> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.blue,
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: GestureDetector(
          onTap: () {

          },
          child: const Text('返回上一个页面'),
        ),
      ),
    );
  }
}

pageBuilder 有一个animation参数,这是Flutter路由管理器提供的,在路由切换时pageBuilder在每个动画帧都会被回调,因此我们可以通过animation对象来自定义过渡动画。

实现自定义路由

无论是MaterialPageRouteCupertinoPageRoute,还是PageRouteBuilder,它们都继承自PageRoute类,而PageRouteBuilder其实只是PageRoute的一个包装,我们可以直接继承PageRoute类来实现自定义路由,上面的例子可以通过如下方式实现:

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: GestureDetector(
          onTap: () {
            Navigator.push(
                context,
                FadeRoute(
                  builder: (context) {
                    return const MyTwoPage(title: "下一个页面"); //路由B
                  },
                  barrierColor: Colors.red,
                  barrierLabel: 'BarrierLabel',
                ));
          },
          child: const Text('跳转下一个页面'),
        ),
      ),
    );
  }
}

class MyTwoPage extends StatefulWidget {
  const MyTwoPage({super.key, required this.title});

  final String title;

  @override
  State<MyTwoPage> createState() => _MyTwoPageState();
}

class _MyTwoPageState extends State<MyTwoPage> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.transparent,
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: GestureDetector(
          onTap: () {},
          child: const Text('返回上一个页面'),
        ),
      ),
    );
  }
}

class FadeRoute extends PageRoute {
  FadeRoute({
    required this.builder,
    this.transitionDuration = const Duration(milliseconds: 300),
    this.opaque = true,
    this.barrierDismissible = false,
    required this.barrierColor,
    required this.barrierLabel,
    this.maintainState = true,
  });

  final WidgetBuilder builder;

  @override
  final Duration transitionDuration;

  @override
  final bool opaque;

  @override
  final bool barrierDismissible;

  @override
  final Color barrierColor;

  @override
  final String barrierLabel;

  @override
  final bool maintainState;

  @override
  Widget buildPage(BuildContext context, Animation<double> animation,
          Animation<double> secondaryAnimation) =>
      builder(context);

  @override
  Widget buildTransitions(BuildContext context, Animation<double> animation,
      Animation<double> secondaryAnimation, Widget child) {
    return FadeTransition(
      opacity: animation,
      child: builder(context),
    );
  }
}

返回页面取消动画

只想在打开新路由时应用动画,而在返回时不使用动画,那么我们在构建过渡动画时就必须判断当前路由isActive属性是否为true,代码如下:

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: GestureDetector(
          onTap: () {
            Navigator.push(
                context,
                FadeRoute(
                  builder: (context) {
                    return const MyTwoPage(title: "下一个页面"); //路由B
                  },
                  barrierColor: Colors.red,
                  barrierLabel: 'BarrierLabel',
                ));
          },
          child: const Text('跳转下一个页面'),
        ),
      ),
    );
  }
}

class MyTwoPage extends StatefulWidget {
  const MyTwoPage({super.key, required this.title});

  final String title;

  @override
  State<MyTwoPage> createState() => _MyTwoPageState();
}

class _MyTwoPageState extends State<MyTwoPage> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.transparent,
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: GestureDetector(
          onTap: () {},
          child: const Text('返回上一个页面'),
        ),
      ),
    );
  }
}

class FadeRoute extends PageRoute {
  FadeRoute({
    required this.builder,
    this.transitionDuration = const Duration(milliseconds: 300),
    this.opaque = true,
    this.barrierDismissible = false,
    required this.barrierColor,
    required this.barrierLabel,
    this.maintainState = true,
  });

  final WidgetBuilder builder;

  @override
  final Duration transitionDuration;

  @override
  final bool opaque;

  @override
  final bool barrierDismissible;

  @override
  final Color barrierColor;

  @override
  final String barrierLabel;

  @override
  final bool maintainState;

  @override
  Widget buildPage(BuildContext context, Animation<double> animation,
          Animation<double> secondaryAnimation) =>
      builder(context);

  @override
  Widget buildTransitions(BuildContext context, Animation<double> animation,
      Animation<double> secondaryAnimation, Widget child) {
    //当前路由被激活,是打开新路由
    if(isActive) {
      return FadeTransition(
        opacity: animation,
        child: builder(context),
      );
    }else{
      //是返回,则不应用过渡动画
      return const Padding(padding: EdgeInsets.zero);
    }
  }
}

案例 切换到分支flutter_animation