วิธีสร้าง TabView โดยใช้ Flutter GetX

Sep 17 2020

ฉันกำลังพยายามสร้างแอปโดยใช้ 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 เพื่อเติมข้อมูลได้อย่างไร

คำตอบ

1 Baker Dec 02 2020 at 12:07

ต่อไปนี้เป็นวิธีแก้ปัญหาในการใช้คอนโทรลเลอร์ 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(),
      ),
    );
  }
}
GuillaumeRoux Sep 17 2020 at 15:54

ดังที่คุณได้กล่าวไว้ในบทความทั้งหมดว่าพวกเขาขยายไป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อย่างใดอย่างหนึ่ง

HamedHamedi Nov 24 2020 at 21:16

ฉันเจอปัญหาที่แน่นอนและแสดงให้เห็นว่ากรอบงาน 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 คุณต้องกำจัดมันอย่างถูกต้อง