วิธีสร้าง TabView โดยใช้ Flutter GetX
ฉันกำลังพยายามสร้างแอปโดยใช้ Flutter ด้วยGetX for State Management และในหนึ่งในหน้าของฉันฉันต้องการสร้างวิดเจ็ต TabView ที่กลางหน้าฉันพบสิ่งต่างๆมากมายที่อธิบายวิธีสร้าง TabView เป็น วิดเจ็ตเป็นโพสต์นี้และบทความนี้แต่สิ่งเหล่านี้ขยายรัฐควบคุมด้วยSingleTickerProviderStateMixin
ตามที่ฉันเข้าใจในการอ่านเอกสารฉันไม่ควรใช้ 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);
}
แต่อาร์กิวเมนต์ VSYNC สำหรับ TabController ไม่สามารถเป็นโมฆะและฉันไม่เข้าใจว่าฉันไม่สามารถรับ TickerProvider เพื่อเติมข้อมูลได้อย่างไร
คำตอบ
ต่อไปนี้เป็นวิธีแก้ปัญหาในการใช้คอนโทรลเลอร์ GetX เพื่อควบคุม TabView หรือไม่
GetTicker
มี SingleTickerProviderMixin เวอร์ชัน Get ซึ่งใช้อินเทอร์เฟซ 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ภายในวิดเจ็ต Stateless:
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 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 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นอกสถานะStateas TabController Manage a ( 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 ไม่พร้อมสำหรับสถานการณ์ที่เป็นไปได้หลายอย่างและคุณควรใช้อย่างระมัดระวัง
วิธีแก้ปัญหาที่ฉันคิดขึ้นมาคือวิดเจ็ต Wrapper ในตัวอย่างที่เสนอฉันใช้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 คุณต้องกำจัดมันอย่างถูกต้อง