Skip to content

base_ui 详细文档

概述

base_ui 是 FluCli 项目模板的核心UI基础架构,通过统一的页面基类和组件封装,为Flutter应用提供标准化的开发模式。本文档将深入介绍base_ui的设计原理、使用方法和最佳实践。

🎯 设计理念

界面设计的三层决策模型

界面设计是一个复杂的过程,涉及多个决策因素,为了确保设计的一致性和可维护性,我们采用了三层决策模型:基础框架选择、内容性质定义和交互方式确定。

1. 基础框架选择:界面类型

界面设计的首要决策是确定整体框架,主要分为两种情况:

  • 有AppBar的页面:适用于需要全局导航和操作的场景
  • 无AppBar的页面:适用于沉浸式内容展示场景

2. 内容性质定义:内容类型

在确定基础框架后,需根据内容性质进行分类:

  • 数据页面:以展示结构化数据为主,如表格、列表等
  • 状态页面:以传递特定状态信息为主,如空状态、加载状态等

3. 交互方式确定:显示方式

最后根据内容体量和交互需求决定显示方式:

  • 可滚动的页面:如列表页等内容较长的场景
  • 不可滚动的页面:如详情页等内容聚焦的场景

基于以上的需求,我们提供一套相对完善的Mixin供开发者使用,开发者可以根据自己的需求选择使用

dart
        HzyScaffolMixin,         // 脚手架混入
        HzyAppBarMixin,          // 导航栏混入
        HzyBodyMixin,            // 主体内容混入
        HzyAbsAttribute,         // 抽象属性混入
        HzyAbstractNetWork,      // 网络请求混入
        HzyNormalLifeCycleAbs,   // 生命周期混入
        HzyAbsState,             // 界面状态混入
        HzyAbstracRefreshWidget, // 刷新组件混入
        HzyAbstracRefreshMehod,  // 刷新触发方法混入
        HzyAbstractNetWork,      // 网络请求混入

通过以上的Mixin,开发者可以快速搭建一个具备基础功能的页面,开发者也可以根据自己的需求,选择混入其他的Mixin,来扩展页面的功能

基于以上的Mixin,base_ui提供两种状态管理实现方案

  • 基于Flutter原生状态管理 base_normal_ui
  • 基于GetX状态管理 base_getx_ui

如果想要了解更多关于Mixin的详细信息,请参考Mixin详细文档

核心目标

  • 统一性:提供一致的页面结构和交互模式
  • 复用性:减少重复代码,提高开发效率
  • 扩展性:支持灵活的自定义和扩展
  • 维护性:降低项目维护成本

注意

  • base_ui 是一个基础架构,不包含具体的业务逻辑。
  • 开发者可以根据项目需求,选择合适的状态管理方案(base_normal_ui 或 base_getx_ui)。

📚 base_ui 基础类

基于开发过程中,频繁使用的场景,提供无状态、有状态以及布局相关的组件基类和界面基类

  • base_normal_ui

基于Flutter原生状态管理方案

  1. 无状态

    • BaseLessV 无状态界面基类
    • BaseLessWidget 无状态组件基类
  2. 有状态

    • BaseStateV 有状态界面基类
    • BaseStateWidget 有状态组件基类
    • BaseListStateV 列表界面基类
    • BaseListStateWidget 列表组件基类
  3. 布局相关

    • BaseLayoutStateV 布局有状态界面基类
    • BaseLayoutStateWidget 布局有状态组件基类
    • BaseLayoutLessV 布局无状态界面基类
    • BaseLayoutLessWidget 布局无状态组件基类
  • base_getx_ui

基于GetX状态管理方案

  1. 界面
  • BaseGetXV 基于GetX界面基类
  • BaseGetXlistV 基于GetX界面列表基类
  1. 控制器
  • BaseGetXController 基于GetX控制器基类
  • BaseGetXListController 基于GetX列表控制器基类

🏗️ base_normal_ui 介绍和使用

BaseStateV - 完整页面基类

BaseStateV 是功能最完整的页面基类,集成了多个Mixin提供丰富功能:

dart
abstract class BaseStateV<T extends StatefulWidget> extends State<T>
    with
        HzyScaffolMixin,      // 脚手架混入
        HzyAppBarMixin,       // 导航栏混入
        HzyBodyMixin,         // 主体内容混入
        HzyAbsAttribute,      // 抽象属性混入
        HzyAbstractNetWork,   // 网络请求混入
        HzyNormalLifeCycleAbs,// 生命周期混入
        WidgetsBindingObserver // 应用生命周期观察者

核心功能特性

功能模块说明使用场景
页面状态管理支持初始化、加载中、成功、失败、空数据状态所有需要状态管理的页面
缺省页面统一的加载、错误、空数据页面网络请求页面
安全区域自动处理刘海屏和底部安全区域全屏页面
生命周期完整的页面和应用生命周期管理需要监听生命周期的页面
主题适配支持亮色/暗色主题切换所有页面

想了解更多base_normal_ui的详细信息,请查看base_normal_ui

🚀 GetX base_getx_ui 介绍和使用

BaseGetXController - 控制器基类

基于GetX的控制器基类,提供响应式状态管理:

dart
class HomeController extends BaseGetXController {
  // 响应式数据
  var userList = <User>[].obs;
  var isLoading = false.obs;
  
  @override
  void onInit() {
    super.onInit();
    loadUsers();
  }

  void loadUsers() async {
    try {
      isLoading.value = true;
      pageState.value = PageState.loadingState;
      
      final users = await UserApi.getUsers();
      userList.value = users;
      
      pageState.value = PageState.successState;
    } catch (e) {
      pageState.value = PageState.errorState;
      placeMsg = '加载失败,请重试';
    } finally {
      isLoading.value = false;
    }
  }

  void refreshData() {
    loadUsers();
  }

  @override
  void tapPlaceHoldWidgetMethod({required CommonPlaceHoldType placeHoldType}) {
    // 缺省页面点击处理
    if (placeHoldType == CommonPlaceHoldType.errorData) {
      loadUsers();
    }
  }
}

BaseGetXV - 视图基类

基于GetX的视图基类,支持响应式UI更新:

dart
class HomePage extends BaseGetXV<HomeController> {
  @override
  Widget createBody(BuildContext context) {
    return Obx(() {
      if (controller.isLoading.value) {
        return Center(child: CircularProgressIndicator());
      }
      
      return ListView.builder(
        itemCount: controller.userList.length,
        itemBuilder: (context, index) {
          final user = controller.userList[index];
          return ListTile(
            title: Text(user.name),
            subtitle: Text(user.email),
            onTap: () => Get.to(() => UserDetailPage(user: user)),
          );
        },
      );
    });
  }

  @override
  String? createAppBarTitle() => '用户列表';

  @override
  bool get isShowAppBar => true;

  @override
  List<Widget>? createAppBarActions() {
    return [
      IconButton(
        icon: Icon(Icons.refresh),
        onPressed: () => controller.refreshData(),
      ),
    ];
  }
}

路由绑定

dart
// 在路由配置中绑定控制器
GetPage(
  name: '/home',
  page: () => HomePage(),
  binding: BindingsBuilder(() {
    Get.lazyPut<HomeController>(() => HomeController());
  }),
),

🎨 主题和样式定制

主题配置

dart
// 在BaseStateV中重写主题相关方法
class _HomePageState extends BaseStateV<HomePage> {
  @override
  Color? createScaffoldBackgroundColor() {
    return Theme.of(context).colorScheme.background;
  }

  @override
  Color? createAppBarBackgroundColor() {
    return Theme.of(context).primaryColor;
  }

  @override
  bool get isDarkStatusBar => Theme.of(context).brightness == Brightness.light;
}

自定义缺省页面

dart
@override
Widget? createEmptyWidget() {
  return CustomEmptyWidget(
    icon: Icons.inbox,
    title: '暂无数据',
    subtitle: '点击刷新重新加载',
    onRefresh: () => loadData(),
  );
}

🔧 高级功能

网络请求集成

dart
class _ApiPageState extends BaseStateV<ApiPage> {
  @override
  void configDefault() {
    super.configDefault();
    // 使用内置网络请求方法
    requestData();
  }

  void requestData() async {
    await requestMethod(
      request: () => ApiService.getData(),
      onSuccess: (data) {
        // 请求成功处理
        setState(() {
          pageState = PageState.successState;
        });
      },
      onError: (error) {
        // 请求失败处理
        setState(() {
          pageState = PageState.errorState;
          errMsg = error.toString();
        });
      },
    );
  }
}

生命周期监听

dart
class _LifeCyclePageState extends BaseStateV<LifeCyclePage> {
  @override
  void configAppLifeCycleResumed() {
    // 应用进入前台
    print('应用进入前台');
    refreshData();
  }

  @override
  void configAppLifeCyclePaused() {
    // 应用进入后台
    print('应用进入后台');
    saveData();
  }

  @override
  void interfaceRenderingCompleted() {
    // 界面渲染完成
    print('界面渲染完成');
    loadInitialData();
  }
}

想了解更多base_getx_ui的详细信息,请查看base_getx_ui

📋 最佳实践

1. 选择合适的基类

场景推荐基类理由
完整页面BaseStateV / BaseGetXV功能完整,支持所有特性
自定义组件BaseStateWidget轻量级,性能更好
列表页面BaseListState / BaseGetXListV内置列表功能
简单页面BaseStateWidget减少不必要的功能

2. 状态管理规范

dart
// 好的做法
class _GoodPageState extends BaseStateV<GoodPage> {
  @override
  void configDefault() {
    super.configDefault();
    // 在这里进行初始化
    initData();
  }

  void initData() async {
    setState(() {
      pageState = PageState.loadingState;
    });
    
    try {
      final data = await loadData();
      setState(() {
        pageState = PageState.successState;
      });
    } catch (e) {
      setState(() {
        pageState = PageState.errorState;
        errMsg = e.toString();
      });
    }
  }
}

3. 错误处理

dart
@override
void tapPlaceHoldWidgetMethod({required CommonPlaceHoldType placeHoldType}) {
  switch (placeHoldType) {
    case CommonPlaceHoldType.errorData:
      // 重新加载数据
      loadData();
      break;
    case CommonPlaceHoldType.emptyData:
      // 跳转到添加页面
      Navigator.push(context, MaterialPageRoute(builder: (_) => AddPage()));
      break;
    case CommonPlaceHoldType.networkError:
      // 检查网络设置
      showNetworkSettingsDialog();
      break;
  }
}

4. 性能优化

dart
class _OptimizedPageState extends BaseStateV<OptimizedPage> {
  @override
  void dispose() {
    // 清理资源
    controller?.dispose();
    timer?.cancel();
    super.dispose();
  }

  @override
  bool get wantKeepAlive => true; // 保持页面状态
}

🚨 常见问题

Q: 如何在base_ui中使用自定义AppBar?

dart
@override
PreferredSizeWidget? createAppBar() {
  return CustomAppBar(
    title: '自定义标题',
    actions: [
      IconButton(
        icon: Icon(Icons.search),
        onPressed: () => showSearch(),
      ),
    ],
  );
}

Q: 如何禁用某些默认功能?

dart
@override
bool get isShowAppBar => false; // 不显示AppBar

@override
bool get isNeedSafe => false; // 不需要安全区域

@override
bool get safeAreaBottom => false; // 底部不需要安全区域

Q: 如何在GetX版本中处理页面跳转?

dart
// 在Controller中
void goToDetail(String id) {
  Get.toNamed('/detail', arguments: {'id': id});
}

// 在View中
ElevatedButton(
  onPressed: () => controller.goToDetail('123'),
  child: Text('查看详情'),
)

通过合理使用base_ui,您可以大幅提升Flutter应用的开发效率和代码质量!

根据 MIT 许可发布。