Как создать TabView с помощью Flutter GetX
Я пытаюсь создать приложение, используя Flutter с GetX для управления состоянием, и на одной из моих страниц мне нужно создать виджет TabView посередине страницы, я уже нашел много вещей, объясняющих, как создать TabView как виджет, так как этот пост и эта статья , но все это расширяет государственный контроллер с SingleTickerProviderStateMixin .
Насколько я понимаю, читая документацию, я не должен использовать StatefulWidgets и подобные состояния, но я не могу понять, как разработать решение с использованием архитектуры 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);
}
Но аргумент VSYNC для TabController не может быть нулевым, и я не понимаю, как я не могу получить TickerProvider для его заполнения.
Ответы
Будет ли следующее решение использования контроллера GetX для управления TabView?
GetTicker
Существует версия Get SingleTickerProviderMixin, которая реализует интерфейс TickerProvider (с использованием того же класса Ticker из Flutter SDK).
У него запоминающееся название: SingleGetTickerProviderMixin
Приведенный ниже пример в основном взят из документации Flutter для TabController .
Из связанного примера StatefulWidget я перенес его содержимое в GetxController ( MyTabController) и добавил смесь SingleGetTickerProviderMixin:
class MyTabController extends GetxController with SingleGetTickerProviderMixin {
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 SingleGetTickerProviderMixin {
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(),
),
);
}
}
Как вы сказали , во всех статьях они проходят Stateс , SingleTickerProviderStateMixinпотому что вы не сможете инициализировать TabControllerза пределами , Stateкак TabController управлять государством ( DOC ).
Обходной путь - не использовать переменную для вашего контроллера и обернуть дерево виджетов TabBarи TabViewвнутри файла 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.
Я столкнулся с точной проблемой, и она показывает, что платформа GetX не готова для многих возможных сценариев, и вы должны использовать ее с осторожностью.
Решение, которое я придумал, - это виджет-оболочка. В предложенном примере я использовал, HookWidgetно вы также можете использовать виджет с отслеживанием состояния.
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. Если вы используете виджет с отслеживанием состояния, вы должны правильно его удалить.