如何使用 Flutter GetX 构建 TabView
How to build a TabView using Flutter GetX
我正在尝试使用带有 GetX for State Management, and in one of my pages i need to build a TabView widget in the middle of the page, i already found a lot of stuff explaining how to build a TabView as widget, as and this article 的 Flutter 构建一个应用程序,但所有这些都扩展了带有 SingleTickerProviderStateMixin[=] 的 State 控制器25=].
据我阅读文档的理解,我不应该像那样使用 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 参数不能为 null,而且我不明白我如何无法获得 TickerProvider 来填充它。
正如您在所有文章中所说,他们用 SingleTickerProviderStateMixin
扩展了 State
,因为您将无法在 State
之外将 TabController
初始化为 TabController
管理状态 (doc).
一种解决方法是不为您的控制器使用变量,并将您的 TabBar
和 TabView
小部件树包装在 DefaultTabController
.
中
这是来自 official doc 的示例:
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
。
我遇到了确切的问题,它表明 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 的帮助下处理控制器。如果您使用的是有状态小部件,则必须正确处理它。
以下是使用 GetX 控制器控制 TabView 的解决方案吗?
GetTicker
有一个 Get 版本的 SingleTickerProviderMixin 实现了 TickerProvider 接口(使用来自 Flutter SDK 的相同 Ticker class)。
它有一个朗朗上口的名字:
GetSingleTickerProviderStateMixin
(更新于 21-12-20:SingleGetTickerProviderMixin
已被最新的 GetX
弃用。感谢评论者指出这一点。)
下面的例子基本上来自Flutter docs for TabController.
我从链接示例的 StatefulWidget 中将其内容移植到 GetxController (MyTabController
) 中并添加了 SingleGetTickerProviderMixin
:
的 mixin
class MyTabController extends GetxController with GetSingleTickerProviderStateMixin {
final List<Tab> myTabs = <Tab>[
Tab(text: 'LEFT'),
Tab(text: 'RIGHT'),
];
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 {
@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.toLowerCase();
return Center(
child: Text(
'This is the $label tab',
style: const TextStyle(fontSize: 36),
),
);
}).toList(),
),
);
}
}
这是应用程序示例的其余部分,只需将其复制并粘贴到 Android Studio / VisualStudio 代码中即可 运行:
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'),
];
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 {
@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.toLowerCase();
return Center(
child: Text(
'This is the $label tab',
style: const TextStyle(fontSize: 36),
),
);
}).toList(),
),
);
}
}
我正在尝试使用带有 GetX for State Management, and in one of my pages i need to build a TabView widget in the middle of the page, i already found a lot of stuff explaining how to build a TabView as widget, as
据我阅读文档的理解,我不应该像那样使用 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 参数不能为 null,而且我不明白我如何无法获得 TickerProvider 来填充它。
正如您在所有文章中所说,他们用 SingleTickerProviderStateMixin
扩展了 State
,因为您将无法在 State
之外将 TabController
初始化为 TabController
管理状态 (doc).
一种解决方法是不为您的控制器使用变量,并将您的 TabBar
和 TabView
小部件树包装在 DefaultTabController
.
这是来自 official doc 的示例:
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
。
我遇到了确切的问题,它表明 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 的帮助下处理控制器。如果您使用的是有状态小部件,则必须正确处理它。
以下是使用 GetX 控制器控制 TabView 的解决方案吗?
GetTicker
有一个 Get 版本的 SingleTickerProviderMixin 实现了 TickerProvider 接口(使用来自 Flutter SDK 的相同 Ticker class)。
它有一个朗朗上口的名字:
GetSingleTickerProviderStateMixin
(更新于 21-12-20:SingleGetTickerProviderMixin
已被最新的 GetX
弃用。感谢评论者指出这一点。)
下面的例子基本上来自Flutter docs for TabController.
我从链接示例的 StatefulWidget 中将其内容移植到 GetxController (MyTabController
) 中并添加了 SingleGetTickerProviderMixin
:
class MyTabController extends GetxController with GetSingleTickerProviderStateMixin {
final List<Tab> myTabs = <Tab>[
Tab(text: 'LEFT'),
Tab(text: 'RIGHT'),
];
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 {
@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.toLowerCase();
return Center(
child: Text(
'This is the $label tab',
style: const TextStyle(fontSize: 36),
),
);
}).toList(),
),
);
}
}
这是应用程序示例的其余部分,只需将其复制并粘贴到 Android Studio / VisualStudio 代码中即可 运行:
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'),
];
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 {
@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.toLowerCase();
return Center(
child: Text(
'This is the $label tab',
style: const TextStyle(fontSize: 36),
),
);
}).toList(),
),
);
}
}