如何使用Flutter GetX构建TabView

19
我正在尝试使用Flutter和GetX进行状态管理构建应用程序,在其中一个页面中,我需要在页面中间构建一个TabView小部件。我已经找到了很多关于如何构建TabView小部件的内容,例如此帖子此文章,但是这些内容都扩展了一个带有SingleTickerProviderStateMixin的State控制器。

根据我阅读的文档,我不应该像那样使用StatefulWidgets和States,但我无法想象如何使用GetX架构来实现解决方案。

我已经在我的页面中尝试了以下内容:

class CourseDetailPage extends StatelessWidget {
  final TabController _tabController = Get.put(TabController(vsync: null, length: 2));
}

并且

class CourseDetailPage extends StatelessWidget {
  final TabController _tabController = TabController(vsync: null, length: 2);
}  

但是TabController的VSYNC参数不能为空,我不知道如何获取TickerProvider来填充它。

3个回答

56
以下是否可以解决使用GetX控制器控制TabView的问题?

GetTicker

SingleTickerProviderMixin有一个Get版本,它实现了TickerProvider接口(使用来自Flutter SDK的相同Ticker类)。

它有一个引人注目的名称: GetSingleTickerProviderStateMixin

(更新于21-12-20:SingleGetTickerProviderMixin已经随着最新的GetX被弃用。感谢评论者指出这一点。)

以下示例基本上来自TabController的Flutter文档

从链接示例的StatefulWidget中,我将其内容移植到了一个GetxController(MyTabController)中,并添加了SingleGetTickerProviderMixin的混合:

class MyTabController extends GetxController with GetSingleTickerProviderStateMixin {
  final List<Tab> myTabs = <Tab>[
    Tab(text: 'LEFT'),
    Tab(text: 'RIGHT'),
  ];

  late TabController controller;

  @override
  void onInit() {
    super.onInit();
    controller = TabController(vsync: this, length: myTabs.length);
  }

  @override
  void onClose() {
    controller.dispose();
    super.onClose();
  }
}

在无状态小部件中使用MyTabController

class MyTabbedWidget extends StatelessWidget {
  const MyTabbedWidget();

  @override
  Widget build(BuildContext context) {
    final MyTabController _tabx = Get.put(MyTabController());
    // ↑ init tab controller

    return Scaffold(
      appBar: AppBar(
        bottom: TabBar(
          controller: _tabx.controller,
          tabs: _tabx.myTabs,
        ),
      ),
      body: TabBarView(
        controller: _tabx.controller,
        children: _tabx.myTabs.map((Tab tab) {
          final String label = tab.text!;
          return Center(
            child: Text(
              'This is the $label tab',
              style: const TextStyle(fontSize: 36),
            ),
          );
        }).toList(),
      ),
    );
  }
}

以下是示例应用程序的其余部分,可直接复制并粘贴到Android Studio / VisualStudio Code中运行:

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('GetX Tab Example'),
      ),
      body: Column(
        children: [
          Expanded(
            flex: 1,
            child: Container(
              alignment: Alignment.center,
              child: Text('Some random stuff'),
            ),
          ),
          Expanded(
            flex: 4,
            child: MyTabbedWidget(),
          )
        ],
      ),
    );
  }
}

class MyTabController extends GetxController with GetSingleTickerProviderStateMixin {
  final List<Tab> myTabs = <Tab>[
    Tab(text: 'LEFT'),
    Tab(text: 'RIGHT'),
  ];

  late TabController controller;

  @override
  void onInit() {
    super.onInit();
    controller = TabController(vsync: this, length: myTabs.length);
  }

  @override
  void onClose() {
    controller.dispose();
    super.onClose();
  }
}

class MyTabbedWidget extends StatelessWidget {
  const MyTabbedWidget();

  @override
  Widget build(BuildContext context) {
    final MyTabController _tabx = Get.put(MyTabController());
    // ↑ init tab controller

    return Scaffold(
      appBar: AppBar(
        bottom: TabBar(
          controller: _tabx.controller,
          tabs: _tabx.myTabs,
        ),
      ),
      body: TabBarView(
        controller: _tabx.controller,
        children: _tabx.myTabs.map((Tab tab) {
          final String label = tab.text!;
          return Center(
            child: Text(
              'This is the $label tab',
              style: const TextStyle(fontSize: 36),
            ),
          );
        }).toList(),
      ),
    );
  }
}

SingleGetTickerProviderMixin现在已经被弃用了。因此,您可以使用GetSingleTickerProviderStateMixin。 - Aditya Patil
2
SingleGetTickerProviderMixinGetSingleTickerProviderStateMixin现在都已经被弃用,你可以从现在开始使用SingleGetTickerProviderMixin - Jay Gadariya
你的代码不支持空安全。 - Sajad Rahmdel

0

就像你在所有文章中所说的那样,他们使用State扩展SingleTickerProviderStateMixin,因为如果不在State内初始化你的TabController,你将无法完成这个操作,因为TabController 管理着一个状态文档)。

一个解决方法是不使用变量来控制你的控制器,而是将你的TabBarTabView小部件树包裹在DefaultTabController中。

以下是来自官方文档的一个示例:

class MyDemo extends StatelessWidget {
  final List<Tab> myTabs = <Tab>[
    Tab(text: 'LEFT'),
    Tab(text: 'RIGHT'),
  ];

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: myTabs.length,
      child: Scaffold(
        appBar: AppBar(
          bottom: TabBar(
            tabs: myTabs,
          ),
        ),
        body: TabBarView(
          children: myTabs.map((Tab tab) {
            final String label = tab.text.toLowerCase();
            return Center(
              child: Text(
                'This is the $label tab',
                style: const TextStyle(fontSize: 36),
              ),
            );
          }).toList(),
        ),
      ),
    );
  }
}

通过这样做,您不需要将您的TabView放在一个State中,但您也不会使用GetX

很好,这个解决方法非常有用,我不得不进行了一些调整,因为我需要将选项卡放置在页面的中央,所以我使用了DefaultTabController作为小部件,效果很好!但是我仍然对可能涉及Getx的解决方案感到好奇... - Patrick Freitas
2
但有时您需要使用TabController,例如以编程方式切换选项卡。那么如何传递TickerProvider呢? - jakub

0

我遇到了同样的问题,这表明GetX框架并没有准备好应对许多可能的情况,因此您应该谨慎使用。

我想出的解决方案是一个包装器小部件。在提出的示例中,我使用了HookWidget,但您也可以自由地使用Stateful小部件。

class TabWidget extends HookWidget {
  final List<Widget> children;
  final RxInt currentTab;
  final int initialIndex;

  TabWidget({
    @required this.children,
    @required this.currentTab,
    @required this.initialIndex,
  });

  @override
  Widget build(BuildContext context) {
    final controller = useTabController(
      initialLength: children.length,
      initialIndex: initialIndex,
    );
    currentTab.listen((page) {
      controller.animateTo(page);
    });
    return TabBarView(controller: controller, children: children);
  }
}

正如您所看到的,有一个Rx变量来控制tabView的状态。因此,您必须从外部传递一个RxInt,每当其值发生更改时,tabView相应地更新。

class TestView extends GetView<TestController> {
  @override
  Widget build(BuildContext context) {
    final screen = 0.obs;
    return SafeArea(
      child: Scaffold(
        appBar: AppBar(
          actions: [
            IconButton(
              icon: Icon(Icons.forward),
              onPressed: () => screen.value = screen.value + 1,
            )
          ],
        ),
        body: TabWidget(
          initialIndex: 1,
          currentTab: screen,
          children: [
             child1,
             child2,
             child3,
             ...,
          ],
        ),
      ),
    );
  }

现在我们通过 HookWidget 来处理控制器。如果您正在使用 Stateful widget,则必须正确地处理它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接