Cách tạo TabView bằng Flutter GetX

Sep 17 2020

Tôi đang cố gắng tạo một ứng dụng bằng Flutter với GetX cho State Management và trong một trong các trang của tôi, tôi cần tạo một tiện ích TabView ở giữa trang, tôi đã tìm thấy rất nhiều thứ giải thích cách tạo một TabView như tiện ích con, như bài đăng này và bài viết này , nhưng tất cả đều mở rộng bộ điều khiển Trạng thái với SingleTickerProviderStateMixin .

Như tôi hiểu khi đọc tài liệu, tôi không nên sử dụng StatefulWidgets và States như vậy, nhưng tôi không thể tìm ra cách xây dựng giải pháp bằng cách sử dụng kiến ​​trúc GetX.

Tôi đã thử trong trang của mình những thứ như:

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

và

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

Nhưng đối số VSYNC cho TabController không thể rỗng và tôi không tìm ra cách tôi không thể lấy TickerProvider để điền nó.

Trả lời

1 Baker Dec 02 2020 at 12:07

Sau đây có phải là giải pháp để sử dụng bộ điều khiển GetX để điều khiển TabView không?

GetTicker

Có một phiên bản Get của SingleTickerProviderMixin triển khai giao diện TickerProvider (sử dụng cùng một lớp Ticker từ Flutter SDK).

Nó có một cái tên hấp dẫn: SingleGetTickerProviderMixin

Ví dụ dưới đây về cơ bản là từ tài liệu Flutter cho TabController .

Từ StatefulWidget của ví dụ được liên kết, tôi đã chuyển nội dung của nó vào một GetxController ( MyTabController) và thêm mixin của 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();
  }
}

Để sử dụng MyTabControllerbên trong tiện ích Không trạng thái:

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(),
      ),
    );
  }
}

Đây là phần còn lại của ví dụ ứng dụng chỉ cần sao chép và dán vào Android Studio / VisualStudio Code để chạy:

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

Như bạn đã nói trong tất cả các bài viết, chúng mở rộng Statevới SingleTickerProviderStateMixinvì bạn sẽ không thể khởi tạo TabControllerbên ngoài trạng thái quản lýState dưới dạng ( doc ).TabController

Một cách giải quyết là không sử dụng một biến cho bộ điều khiển của bạn và bọc cây TabBarvà TabViewwidget của bạn bên trong a DefaultTabController.

Đây là một ví dụ từ tài liệu chính thức :

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(),
        ),
      ),
    );
  }
}

Bằng cách đó, bạn sẽ không cần phải có TabViewbên trong Statenhưng bạn cũng sẽ không sử dụng GetX.

HamedHamedi Nov 24 2020 at 21:16

Tôi đã gặp vấn đề chính xác và nó cho thấy rằng khung GetX chưa sẵn sàng cho nhiều trường hợp có thể xảy ra và bạn nên sử dụng nó một cách thận trọng.

Giải pháp tôi đưa ra là một widget trình bao bọc. Trong ví dụ đề xuất tôi đã sử dụng HookWidgetnhưng bạn cũng có thể tự do sử dụng tiện ích 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);
  }
}

Như bạn có thể thấy, có một biến Rx điều khiển trạng thái của tabView. Vì vậy, bạn phải chuyển một RxInt từ bên ngoài và bất cứ khi nào giá trị của nó thay đổi, tabView sẽ cập nhật tương ứng.

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,
             ...,
          ],
        ),
      ),
    );
  }

Ngay bây giờ chúng tôi chăm sóc bộ điều khiển bằng sự trợ giúp của HookWidget. Nếu bạn đang sử dụng tiện ích Stateful, bạn phải loại bỏ nó một cách chính xác.