Skip to content

GetX UI 基础组件

概述

本文档详细介绍了 FluCli 项目中基于 GetX 状态管理的基础 UI 组件,包括页面基类、列表页面基类和控制器基类的使用方法。通过这些基础组件,您可以快速构建具有统一风格和完整功能的 Flutter 页面。

基础页面组件

BaseGetXV 页面基类

BaseGetXV 是所有 GetX 页面的基类,它继承自 GetView 并混入了多个 Mixin,提供了完整的页面功能:

dart
// lib/common/base_ui/base_get_ui/base_getx_v.dart
abstract class BaseGetXV<T extends BaseGetXController> extends GetView<T>
    with HzyScaffolMixin, HzyAppBarMixin, HzyBodyMixin, HzyAbsAttribute {
  BaseGetXV({super.key});

  @override
  Widget build(BuildContext context) {
    Widget body = createBuild(context: context);
    return body;
  }

  @override
  Widget createBuild({required BuildContext context}) {
    return Obx(() => createScaffolWidget(context: context));
  }
}

核心特性

  1. 响应式状态管理:使用 Obx 自动监听状态变化
  2. 统一页面结构:提供标准的 Scaffold、AppBar、Body 结构
  3. 生命周期管理:集成完整的页面生命周期
  4. 缺省页面支持:内置加载、空数据、错误页面
  5. 安全区域处理:自动处理刘海屏适配

使用示例

创建一个简单的 GetX 页面:

dart
// lib/pages/demo_page.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:your_project/common/base_ui/base_get_ui/base_getx_v.dart';
import 'package:your_project/vm/demo_controller.dart';

class DemoPage extends BaseGetXV<DemoController> {
  DemoPage({Key? key}) : super(key: key);

  @override
  DemoController get controller => Get.put(DemoController());

  @override
  String getPageTitle() {
    return '演示页面';
  }

  @override
  Widget createBody({required BuildContext context, BoxConstraints? constraints}) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Obx(() => Text(
            '计数: ${controller.count}',
            style: Theme.of(context).textTheme.headlineMedium,
          )),
          const SizedBox(height: 20),
          ElevatedButton(
            onPressed: controller.increment,
            child: const Text('增加'),
          ),
        ],
      ),
    );
  }
}

缺省页面配置

BaseGetXV 支持多种页面状态的缺省页面,您可以通过重写相应方法来自定义:

dart
class DemoPage extends BaseGetXV<DemoController> {
  // 自定义空数据页面
  @override
  Widget? createEmptyWidget() {
    return PlaceHoldPage(
      placeHoldType: controller.placeHoldType,
      msg: controller.placeMsg,
      btnMsg: controller.placeBtnMsg,
      onTap: () {
        controller.tapPlaceHoldWidgetMethod(
          placeHoldType: controller.placeHoldType,
        );
      },
    );
  }

  // 自定义加载页面
  @override
  Widget? createLoadingWidget() {
    return const Center(
      child: CircularProgressIndicator(),
    );
  }

  // 自定义错误页面
  @override
  Widget? createErrorWidget() {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const Icon(Icons.error, size: 64, color: Colors.red),
          const SizedBox(height: 16),
          const Text('加载失败'),
          const SizedBox(height: 16),
          ElevatedButton(
            onPressed: controller.retry,
            child: const Text('重试'),
          ),
        ],
      ),
    );
  }
}

页面配置选项

您可以通过重写以下方法来配置页面的各种属性:

dart
class DemoPage extends BaseGetXV<DemoController> {
  // 页面标题
  @override
  String getPageTitle() => '我的页面';

  // 是否显示导航栏
  @override
  bool get showAppBar => true;

  // 是否显示返回按钮
  @override
  bool get showLeading => true;

  // 背景颜色
  @override
  Color? get backgroundColor => Colors.white;

  // 是否使用安全区域
  @override
  bool get useSafeArea => true;

  // 导航栏操作按钮
  @override
  List<Widget>? get actions => [
    IconButton(
      icon: const Icon(Icons.settings),
      onPressed: () => Get.toNamed('/settings'),
    ),
  ];
}

列表页面组件

BaseGetXlistV 列表基类

BaseGetXlistV 是专门为列表页面设计的基类,它在 BaseGetXV 的基础上集成了下拉刷新和上拉加载功能:

dart
// lib/common/base_ui/base_get_ui/base_getx_list_v.dart
abstract class BaseGetXlistV<T extends BaseGetXListController> extends BaseGetXV<T>
    with HzyAbstracRefreshWidget {
  BaseGetXlistV({super.key});

  @override
  Widget createBody({
    required BuildContext context,
    BoxConstraints? constraints,
  }) {
    return createRefreshWidget(context);
  }

  /// 创建列表刷新组件
  @override
  Widget createRefreshWidget(BuildContext context) {
    return EasyRefresh(
      controller: controller.refreshController,
      onRefresh: () async {
        controller.configRefresh();
      },
      onLoad: () async {
        controller.configLoading();
      },
      header: createHeader(),
      footer: createFooter(),
      child: createRefreshChild(context),
    );
  }

  /// 创建列表内容
  Widget createRefreshChild(BuildContext context) {
    return createListView(context);
  }

  /// 需要子类实现的列表视图
  Widget createListView(BuildContext context);
}

列表页面使用示例

创建一个完整的列表页面:

dart
// lib/pages/demo_list_page.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:your_project/common/base_ui/base_get_ui/base_getx_list_v.dart';
import 'package:your_project/vm/demo_list_controller.dart';

class DemoListPage extends BaseGetXlistV<DemoListController> {
  DemoListPage({Key? key}) : super(key: key);

  @override
  DemoListController get controller => Get.put(DemoListController());

  @override
  String getPageTitle() => '演示列表';

  @override
  Widget createListView(BuildContext context) {
    return Obx(() {
      if (controller.dataList.isEmpty) {
        return const Center(
          child: Text('暂无数据'),
        );
      }
      
      return ListView.builder(
        itemCount: controller.dataList.length,
        itemBuilder: (context, index) {
          final item = controller.dataList[index];
          return ListTile(
            title: Text(item.title),
            subtitle: Text(item.subtitle),
            onTap: () => controller.onItemTap(item),
          );
        },
      );
    });
  }
}

列表状态管理

列表页面支持以下状态:

  • 加载中:首次进入页面时显示
  • 有数据:正常显示列表内容
  • 空数据:没有数据时显示空状态页
  • 加载更多:上拉时显示加载更多状态
  • 没有更多:所有数据加载完成时显示
  • 加载失败:网络请求失败时显示错误页面

自定义刷新组件

您可以自定义下拉刷新和上拉加载的样式:

dart
class CustomListPage extends BaseGetXlistV<CustomListController> {
  @override
  Header? createHeader() {
    return const ClassicHeader(
      dragText: '下拉刷新',
      armedText: '释放刷新',
      readyText: '正在刷新...',
      processingText: '正在刷新...',
      processedText: '刷新完成',
      noMoreText: '没有更多',
      failedText: '刷新失败',
      messageText: '最后更新于 %T',
    );
  }

  @override
  Footer? createFooter() {
    return controller.controlFinishLoad
        ? const ClassicFooter(
            dragText: '上拉加载',
            armedText: '释放加载',
            readyText: '正在加载...',
            processingText: '正在加载...',
            processedText: '加载完成',
            noMoreText: '没有更多',
            failedText: '加载失败',
          )
        : const NotLoadFooter();
  }
}

控制器组件

BaseGetXController 基础控制器

BaseGetXController 是所有 GetX 控制器的基类,它集成了网络请求、生命周期管理和页面状态管理功能:

dart
// lib/common/base_ui/base_get_ui/base_getx_controller.dart
abstract class BaseGetXController extends GetxController
    with HzyAbstractNetWork, WidgetsBindingObserver, HzyNormalLifeCycleAbs {
  
  /// 缺省页配置
  final RxString placeMsg = ''.obs;
  final RxString placeBtnMsg = ''.obs;
  final Rx<PlaceHoldType> placeHoldType = PlaceHoldType.empty.obs;
  
  /// 页面状态
  final Rx<PageState> pageState = PageState.loading.obs;
  
  /// 导航栏配置
  final RxBool showAppBar = true.obs;
  final RxString appBarTitle = ''.obs;
  
  /// 安全区域配置
  final RxBool useSafeArea = true.obs;
  
  @override
  void onInit() {
    super.onInit();
    WidgetsBinding.instance.addObserver(this);
    initData();
  }
  
  @override
  void onClose() {
    WidgetsBinding.instance.removeObserver(this);
    super.onClose();
  }
  
  /// 初始化数据
  void initData() {
    // 子类重写实现具体的初始化逻辑
  }
  
  /// 网络请求方法
  Future<void> getNetWorkData() async {
    // 子类重写实现具体的网络请求逻辑
  }
  
  /// 缺省页点击事件
  void tapPlaceHoldWidgetMethod({required PlaceHoldType placeHoldType}) {
    switch (placeHoldType) {
      case PlaceHoldType.netWorkError:
      case PlaceHoldType.empty:
        getNetWorkData();
        break;
      default:
        break;
    }
  }
}

控制器使用示例

创建一个简单的控制器:

dart
// lib/vm/demo_controller.dart
import 'package:get/get.dart';
import 'package:your_project/common/base_ui/base_get_ui/base_getx_controller.dart';

class DemoController extends BaseGetXController {
  final RxInt count = 0.obs;
  final RxString message = 'Hello World'.obs;
  
  @override
  void initData() {
    super.initData();
    // 初始化页面数据
    loadData();
  }
  
  void increment() {
    count.value++;
  }
  
  void updateMessage(String newMessage) {
    message.value = newMessage;
  }
  
  @override
  Future<void> getNetWorkData() async {
    try {
      pageState.value = PageState.loading;
      
      // 模拟网络请求
      await Future.delayed(const Duration(seconds: 2));
      
      // 请求成功
      pageState.value = PageState.success;
      message.value = '数据加载成功';
    } catch (e) {
      // 请求失败
      pageState.value = PageState.error;
      placeHoldType.value = PlaceHoldType.netWorkError;
      placeMsg.value = '网络请求失败';
      placeBtnMsg.value = '重试';
    }
  }
  
  void loadData() {
    getNetWorkData();
  }
  
  void retry() {
    getNetWorkData();
  }
}

BaseGetXListController 列表控制器

专门为列表页面设计的控制器基类:

dart
// lib/common/base_ui/base_get_ui/base_getx_list_controller.dart
abstract class BaseGetXListController<T> extends BaseGetXController {
  /// 刷新控制器
  late EasyRefreshController refreshController;
  
  /// 数据列表
  final RxList<T> dataList = <T>[].obs;
  
  /// 分页参数
  final RxInt currentPage = 1.obs;
  final RxInt pageSize = 20.obs;
  final RxBool hasMore = true.obs;
  final RxBool controlFinishLoad = true.obs;
  
  @override
  void onInit() {
    super.onInit();
    refreshController = EasyRefreshController(
      controlFinishRefresh: true,
      controlFinishLoad: controlFinishLoad.value,
    );
  }
  
  @override
  void onClose() {
    refreshController.dispose();
    super.onClose();
  }
  
  /// 下拉刷新
  Future<void> configRefresh() async {
    currentPage.value = 1;
    hasMore.value = true;
    await getNetWorkData();
    refreshController.finishRefresh();
  }
  
  /// 上拉加载
  Future<void> configLoading() async {
    if (!hasMore.value) {
      refreshController.finishLoad(IndicatorResult.noMore);
      return;
    }
    
    currentPage.value++;
    await getNetWorkData();
    
    if (hasMore.value) {
      refreshController.finishLoad(IndicatorResult.success);
    } else {
      refreshController.finishLoad(IndicatorResult.noMore);
    }
  }
  
  /// 处理列表数据
  void handleListData(List<T> newData, {bool isRefresh = false}) {
    if (isRefresh) {
      dataList.clear();
    }
    
    dataList.addAll(newData);
    
    // 判断是否还有更多数据
    hasMore.value = newData.length >= pageSize.value;
    
    // 更新页面状态
    if (dataList.isEmpty) {
      pageState.value = PageState.empty;
      placeHoldType.value = PlaceHoldType.empty;
      placeMsg.value = '暂无数据';
    } else {
      pageState.value = PageState.success;
    }
  }
}

列表控制器使用示例

dart
// lib/vm/demo_list_controller.dart
import 'package:get/get.dart';
import 'package:your_project/common/base_ui/base_get_ui/base_getx_list_controller.dart';
import 'package:your_project/models/demo_model.dart';
import 'package:your_project/services/api_service.dart';

class DemoListController extends BaseGetXListController<DemoModel> {
  final ApiService _apiService = Get.find<ApiService>();
  
  @override
  Future<void> getNetWorkData() async {
    try {
      if (currentPage.value == 1) {
        pageState.value = PageState.loading;
      }
      
      final response = await _apiService.getDemoList(
        page: currentPage.value,
        pageSize: pageSize.value,
      );
      
      handleListData(
        response.data,
        isRefresh: currentPage.value == 1,
      );
    } catch (e) {
      if (currentPage.value == 1) {
        pageState.value = PageState.error;
        placeHoldType.value = PlaceHoldType.netWorkError;
        placeMsg.value = '加载失败';
        placeBtnMsg.value = '重试';
      }
    }
  }
  
  void onItemTap(DemoModel item) {
    Get.toNamed('/detail', arguments: item);
  }
}

最佳实践

1. 状态管理建议

  • 使用 Obx 进行局部刷新:只在需要响应状态变化的 Widget 上使用 Obx
  • 合理使用 GetBuilder:对于复杂页面,可以使用 GetBuilder 进行更精确的控制
  • 避免过度使用响应式变量:不是所有变量都需要是响应式的

2. 生命周期管理

dart
class MyController extends BaseGetXController {
  @override
  void onInit() {
    super.onInit();
    // 页面初始化时执行
    print('页面初始化');
  }
  
  @override
  void onReady() {
    super.onReady();
    // 页面准备完成时执行
    print('页面准备完成');
  }
  
  @override
  void onClose() {
    // 页面销毁时执行清理工作
    print('页面销毁');
    super.onClose();
  }
}

3. 错误处理

dart
class MyController extends BaseGetXController {
  @override
  Future<void> getNetWorkData() async {
    try {
      pageState.value = PageState.loading;
      
      final result = await apiService.getData();
      
      if (result.isSuccess) {
        pageState.value = PageState.success;
        // 处理成功数据
      } else {
        _handleError(result.message);
      }
    } catch (e) {
      _handleError(e.toString());
    }
  }
  
  void _handleError(String message) {
    pageState.value = PageState.error;
    placeHoldType.value = PlaceHoldType.netWorkError;
    placeMsg.value = message;
    placeBtnMsg.value = '重试';
  }
}

快速开始

使用 FluCli 提供的dart 代码块 快速创建 GetX 页面:

代码片段描述使用场景
glvc自带上拉加载和下拉刷新功能的列表界面和控制器需要分页加载数据的完整页面
gvc自带生命周期管理的有状态界面和控制器需要管理状态的完整页面
glv自带上拉加载和下拉刷新功能的列表界面需要分页加载数据的完整页面
gv自带生命周期管理的有状态界面需要管理状态的完整页面
glc自带上拉加载和下拉刷新功能的控制器列表控制器
gc自带生命周期管理的有状态控制器状态控制器

提示

更多代码块使用方法,请参考 Dart 代码片段速查手册

相关链接

根据 MIT 许可发布。