如何在 TabBar 视图之间使用英雄动画?

Erf*_*koo 5 dart flutter

我的项目中有 3 条路线和许多英雄动画。现在,我想使用具有三个选项卡的路线在页面之间轻松滑动,但这些更改禁用了我所有的英雄动画。我使用TabBarViewTabController喜欢所有其他材料TabBar实现的示例。

这个问题有什么解决办法吗?

Ben*_*ler 4

为了使Hero动画正常工作,您需要在Navigator堆栈上的页面之间导航。不幸的是, aTabBarView不使用 aNavigator在选项卡之间切换,因此我们必须实现自己的自定义选项卡视图小部件,该小部件确实使用 aNavigator以使英雄动画正常工作。

让我们首先制作一个模仿 a 的滑动过渡动画的页面路由TabBarView

/// A [PageRoute] that mimics the transition animation of a [TabBarView].
class TabPageRoute extends PageRoute {
  final TabController tabController;
  final Widget child;

  TabPageRoute({
    required this.tabController,
    required this.child,
  });

  @override
  Color? get barrierColor => null;

  @override
  String? get barrierLabel => null;

  @override
  Widget buildPage(BuildContext context, Animation<double> animation,
      Animation<double> secondaryAnimation) {
    return child;
  }

  Animation<double> applyEasing(Animation<double> animation) =>
      CurvedAnimation(parent: animation, curve: Curves.ease);

  @override
  Widget buildTransitions(BuildContext context, Animation<double> animation,
      Animation<double> secondaryAnimation, Widget child) {
    final isAnimatingNext = tabController.index > tabController.previousIndex;

    // Animates the page when it moves between the middle of the screen and the
    // right of the screen.
    final rightMiddleTween = Tween<Offset>(
      begin: isAnimatingNext ? const Offset(1, 0) : Offset.zero,
      end: isAnimatingNext ? Offset.zero : const Offset(1, 0),
    );

    // Animates the page when it moves between the middle of the screen and the
    // left of the screen.
    final middleLeftTween = Tween<Offset>(
      begin: isAnimatingNext ? Offset.zero : const Offset(-1, 0),
      end: isAnimatingNext ? const Offset(-1, 0) : Offset.zero,
    );

    return SlideTransition(
      position: rightMiddleTween.animate(applyEasing(animation)),
      child: SlideTransition(
        position: middleLeftTween.animate(applyEasing(secondaryAnimation)),
        child: child,
      ),
    );
  }

  @override
  // If you want the state of each of your tab pages to be preserved when that
  // page isn't visible, keep this set to true. Otherwise, set it to false.
  bool get maintainState => true;

  @override
  // This seems to be the default transition duration that TabBars use.
  Duration get transitionDuration => const Duration(milliseconds: 300);
}
Run Code Online (Sandbox Code Playgroud)

现在让我们创建一个自定义选项卡视图,使用 aNavigator和 newTabPageRoute在选项卡之间切换:

class HeroTabBarView extends StatefulWidget {
  final List<Widget> pages;

  // Just like a normal TabView, you need to either have a parent
  // DefaultTabController or pass a TabController.
  final TabController? controller;

  const HeroTabBarView({
    super.key,
    required this.pages,
    this.controller,
  });

  @override
  State<HeroTabBarView> createState() => _HeroTabBarViewState();
}

class _HeroTabBarViewState extends State<HeroTabBarView> {
  late TabController _controller;
  final _navigatorKey = GlobalKey<NavigatorState>();

  @override
  void didChangeDependencies() {
    final resolvedController =
        widget.controller ?? DefaultTabController.maybeOf(context);

    if (resolvedController == null) {
      throw FlutterError(
        'No TabController for ${widget.runtimeType}.\n'
            'When creating a ${widget.runtimeType}, you must either provide an explicit '
            'TabController using the "controller" property, or you must ensure that there '
            'is a DefaultTabController above the ${widget.runtimeType}.\n'
            'In this case, there was neither an explicit controller nor a default controller.',
      );
    }

    _controller = resolvedController;
    _controller.addListener(_switchPage);

    super.didChangeDependencies();
  }

  @override
  void dispose() {
    _controller.removeListener(_switchPage);
    super.dispose();
  }

  void _switchPage() {
    // Use the route's name as the index of the page to switch to.
    _navigatorKey.currentState!.pushReplacementNamed('${_controller.index}');
  }

  @override
  Widget build(BuildContext context) {
    // Use a nested Navigator so that switching tabs involves pushing a
    // new page onto a Navigator stack without changing the general
    // navigation state of your app.
    return Navigator(
      key: _navigatorKey,
      initialRoute: '${_controller.index}',
      // This is required in order to make Hero animations work in a
      // nested navigator.
      observers: [HeroController()],
      onGenerateRoute: (settings) => TabPageRoute(
        tabController: _controller,
        child: widget.pages[int.parse(settings.name!)],
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以像使用普通的 一样使用HeroTabBarViewa 。确保您有一个包含您的和 的父级,或者您将自己的父级传递给您的和。TabBarTabBarViewDefaultTabControllerTabBarHeroTabBarViewTabControllerTabBarHeroTabBarView

class TabBarDemo extends StatelessWidget {
  const TabBarDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DefaultTabController(
        length: 3,
        child: Scaffold(
          appBar: AppBar(
            bottom: const TabBar(
              // Change these to whatever icons you want.
              tabs: [
                Tab(icon: Icon(Icons.directions_car)),
                Tab(icon: Icon(Icons.directions_transit)),
                Tab(icon: Icon(Icons.directions_bike)),
              ],
            ),
            title: const Text('Tabs Demo'),
          ),
          body: const HeroTabBarView(
            // Replace with the pages you want for your tabs.
            pages: [PageOne(), PageTwo(), PageThree()],
          ),
        ),
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

结果

屏幕录制

交互示例

您可以通过 DartPad 上的交互式示例来查看此代码的实际运行情况。