Skip to content

基础组件

概述

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

基础页面组件

BaseStateV 页面基类

BaseStateV 是所有原生状态管理页面的基类,它继承自 State 并混入了多个 Mixin,提供了完整的页面功能:

dart
// lib/common/base_ui/base_normal_ui/base_state.dart
abstract class BaseStateV<T extends StatefulWidget> extends State<T>
    with
        HzyScaffolMixin,      // 脚手架混入
        HzyAppBarMixin,       // 导航栏混入
        HzyBodyMixin,         // 主体内容混入
        HzyAbsAttribute,      // 抽象属性混入
        HzyAbstractNetWork,   // 网络请求混入
        HzyNormalLifeCycleAbs,// 生命周期混入
        WidgetsBindingObserver // 应用生命周期观察者
{
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      configWidgetRenderingCompleted();
    });
    configDefault();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return createScaffolWidget(context: context);
  }
}

核心特性

  1. 原生状态管理:使用 Flutter 原生的 setState 进行状态管理
  2. 统一页面结构:提供标准的 Scaffold、AppBar、Body 结构
  3. 完整生命周期:集成页面和应用生命周期管理
  4. 缺省页面支持:内置加载、空数据、错误页面
  5. 安全区域处理:自动处理刘海屏适配
  6. 网络请求集成:内置网络请求状态管理

使用示例

创建一个简单的原生状态管理页面:

dart
// lib/pages/demo_page.dart
import 'package:flutter/material.dart';
import 'package:your_project/common/base_ui/base_normal_ui/base_state.dart';

class DemoPage extends StatefulWidget {
  const DemoPage({Key? key}) : super(key: key);

  @override
  State<DemoPage> createState() => _DemoPageState();
}

class _DemoPageState extends BaseStateV<DemoPage> {
  int count = 0;
  String message = 'Hello World';
  
  @override
  void configDefault() {
     super.configDefault();
     // 初始化页面数据
     loadData();
   }
   
   @override
   String? createAppBarTitle() => '演示页面';
   
   @override
   bool get isShowAppBar => true;
   
   @override
   Widget createBody(BuildContext context) {
     return Center(
       child: Column(
         mainAxisAlignment: MainAxisAlignment.center,
         children: [
           Text(
             '计数: $count',
             style: Theme.of(context).textTheme.headlineMedium,
           ),
           const SizedBox(height: 20),
           Text(
             message,
             style: Theme.of(context).textTheme.bodyLarge,
           ),
           const SizedBox(height: 20),
           ElevatedButton(
             onPressed: increment,
             child: const Text('增加'),
           ),
         ],
       ),
     );
   }
   
   void increment() {
     setState(() {
       count++;
     });
   }
   
   Future<void> loadData() async {
     setState(() {
       pageState = PageState.loadingState;
     });
     
     try {
       // 模拟网络请求
       await Future.delayed(const Duration(seconds: 2));
       
       setState(() {
         pageState = PageState.dataFetchState;
         message = '数据加载成功';
       });
     } catch (e) {
       setState(() {
         pageState = PageState.errorState;
         errMsg = '网络请求失败';
       });
     }
   }
   
   @override
   void tapPlaceHoldWidgetMethod({required CommonPlaceHoldType placeHoldType}) {
     if (placeHoldType == CommonPlaceHoldType.errorData) {
       loadData();
     }
   }
 }

缺省页面配置

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

dart
class DemoPage extends BaseStateV<DemoPage> {
  // 自定义空数据页面
  @override
  Widget? createEmptyWidget() {
    return const Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.inbox, size: 64, color: Colors.grey),
          SizedBox(height: 16),
          Text('暂无数据'),
        ],
      ),
    );
  }

  // 自定义加载页面
  @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),
          Text(errMsg ?? '加载失败'),
          const SizedBox(height: 16),
          ElevatedButton(
            onPressed: () => loadData(),
            child: const Text('重试'),
          ),
        ],
      ),
    );
  }
}

页面配置选项

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

dart
class DemoPage extends BaseStateV<DemoPage> {
  // 页面标题
  @override
  String? createAppBarTitle() => '我的页面';

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

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

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

  // 导航栏操作按钮
  @override
  List<Widget>? createAppBarActions() => [
    IconButton(
      icon: const Icon(Icons.settings),
      onPressed: () => Navigator.pushNamed(context, '/settings'),
    ),
  ];

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

BaseListStateV 列表基类

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

核心特性

  • 下拉刷新:支持自定义刷新样式和逻辑
  • 上拉加载:支持分页加载更多数据
  • 状态管理:自动管理列表的加载、刷新、错误状态
  • 空数据处理:内置空列表页面
  • 网络请求:集成网络请求状态管理

使用示例

创建一个带有刷新功能的列表页面:

dart
// lib/pages/user_list_page.dart
import 'package:flutter/material.dart';
import 'package:your_project/common/base_ui/base_normal_ui/base_list_state.dart';

class UserListPage extends StatefulWidget {
  const UserListPage({Key? key}) : super(key: key);

  @override
  State<UserListPage> createState() => _UserListPageState();
}

class _UserListPageState extends BaseListStateV<UserListPage> {
  List<User> userList = [];
  int currentPage = 1;
  
  @override
  void configDefault() {
    super.configDefault();
    loadData();
  }
  
  @override
  String? createAppBarTitle() => '用户列表';
  
  @override
  Widget createRefreshChild() {
    return ListView.builder(
      itemCount: userList.length,
      itemBuilder: (context, index) {
        final user = userList[index];
        return ListTile(
          leading: CircleAvatar(
            backgroundImage: NetworkImage(user.avatar),
          ),
          title: Text(user.name),
          subtitle: Text(user.email),
          onTap: () => _onUserTap(user),
        );
      },
    );
  }
  
  @override
  Future<void> configRefresh() async {
    currentPage = 1;
    await loadData();
  }
  
  @override
  Future<void> configLoading() async {
    currentPage++;
    await loadData();
  }
  
  Future<void> loadData() async {
    try {
      setState(() {
        if (currentPage == 1) {
          pageState = PageState.loadingState;
        }
      });
      
      // 模拟网络请求
      final response = await UserApi.getUserList(
        page: currentPage,
        pageSize: 20,
      );
      
      setState(() {
        if (currentPage == 1) {
          userList = response.data;
        } else {
          userList.addAll(response.data);
        }
        
        // 设置页面状态
        if (userList.isEmpty) {
          pageState = PageState.emptyDataState;
        } else {
          pageState = PageState.dataFetchState;
        }
        
        // 设置加载更多状态
        if (response.hasMore) {
          configEndRefresh(IndicatorResult.success);
        } else {
          configEndRefresh(IndicatorResult.noMore);
        }
      });
    } catch (e) {
      setState(() {
        pageState = PageState.errorState;
        errMsg = e.toString();
        configEndRefresh(IndicatorResult.fail);
      });
    }
  }
  
  void _onUserTap(User user) {
    Navigator.pushNamed(
      context,
      '/user_detail',
      arguments: user.id,
    );
  }
  
  @override
  void tapPlaceHoldWidgetMethod({required CommonPlaceHoldType placeHoldType}) {
    if (placeHoldType == CommonPlaceHoldType.errorData) {
      loadData();
    }
  }
}

class User {
  final String id;
  final String name;
  final String email;
  final String avatar;
  
  User({
    required this.id,
    required this.name,
    required this.email,
    required this.avatar,
  });
}

自定义刷新组件

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

dart
class CustomListPage extends BaseListStateV<CustomListPage> {
  @override
  Widget createHeader() {
    return const ClassicHeader(
      refreshText: '下拉刷新',
      refreshingText: '正在刷新...',
      refreshedText: '刷新完成',
      releaseText: '释放刷新',
    );
  }
  
  @override
  Widget createFooter() {
    return const ClassicFooter(
      loadText: '上拉加载',
      loadingText: '正在加载...',
      loadedText: '加载完成',
      noMoreText: '没有更多数据',
    );
  }
}

列表状态管理

BaseListStateV 支持以下列表状态:

  • 初始状态:页面首次加载
  • 加载中:正在获取数据
  • 有数据:成功获取到数据
  • 空数据:没有数据时显示空页面
  • 错误状态:网络请求失败
  • 刷新中:下拉刷新进行中
  • 加载更多:上拉加载更多数据
  • 没有更多:所有数据已加载完成

与 GetX 方案对比

特性base_normal_uibase_getx_ui
状态管理Flutter 原生 setStateGetX 响应式状态管理
代码复杂度中等,需要手动管理状态较低,自动响应式更新
学习成本低(仅需了解 Flutter 基础)中(需要学习 GetX 概念)
性能优秀(直接使用原生机制)良好(GetX 内部优化)
内存占用较低中等
适用场景中小型项目、Flutter 初学者大型项目、复杂状态管理
依赖无第三方依赖依赖 GetX 包

选择建议

选择 base_normal_ui 当:

  • 项目规模较小
  • 团队对 Flutter 原生开发更熟悉
  • 不希望引入额外依赖
  • 状态管理相对简单

选择 base_getx_ui 当:

  • 项目规模较大
  • 需要复杂的状态管理
  • 团队熟悉响应式编程
  • 希望减少样板代码

最佳实践

状态管理建议

dart
class BestPracticePage extends BaseStateV<BestPracticePage> {
  // 1. 合理使用状态变量
  bool _isLoading = false;
  String? _errorMessage;
  List<DataModel> _dataList = [];
  
  // 2. 封装状态更新方法
  void _updateLoadingState(bool loading) {
    if (_isLoading != loading) {
      setState(() {
        _isLoading = loading;
      });
    }
  }
  
  // 3. 统一错误处理
  void _handleError(dynamic error) {
    setState(() {
      _isLoading = false;
      _errorMessage = error.toString();
      pageState = PageState.errorState;
    });
  }
  
  // 4. 数据加载最佳实践
  Future<void> _loadData() async {
    _updateLoadingState(true);
    
    try {
      final data = await ApiService.getData();
      setState(() {
        _dataList = data;
        _isLoading = false;
        _errorMessage = null;
        pageState = data.isEmpty 
            ? PageState.emptyDataState 
            : PageState.dataFetchState;
      });
    } catch (e) {
      _handleError(e);
    }
  }
}

生命周期管理

dart
class LifecyclePage extends BaseStateV<LifecyclePage> {
  Timer? _timer;
  StreamSubscription? _subscription;
  
  @override
  void configDefault() {
    super.configDefault();
    _initializeResources();
  }
  
  @override
  void dispose() {
    // 清理资源
    _timer?.cancel();
    _subscription?.cancel();
    super.dispose();
  }
  
  void _initializeResources() {
    // 初始化定时器
    _timer = Timer.periodic(Duration(seconds: 30), (timer) {
      _refreshData();
    });
    
    // 监听数据流
    _subscription = DataStream.listen((data) {
      setState(() {
        // 更新UI
      });
    });
  }
}

错误处理

dart
class ErrorHandlingPage extends BaseStateV<ErrorHandlingPage> {
  @override
  Widget? createErrorWidget() {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.error_outline, size: 64, color: Colors.red),
          SizedBox(height: 16),
          Text(
            errMsg ?? '加载失败',
            style: TextStyle(fontSize: 16),
          ),
          SizedBox(height: 16),
          ElevatedButton(
            onPressed: _retry,
            child: Text('重试'),
          ),
        ],
      ),
    );
  }
  
  void _retry() {
    setState(() {
      pageState = PageState.loadingState;
    });
    _loadData();
  }
}

快速开始

使用 FluCli 提供的dart 代码块 快速创建基于原生状态管理的页面:

代码片段描述使用场景
stLV自带上拉加载和下拉刷新功能的列表界面需要分页加载数据的完整页面
stLW自带上拉加载和下拉刷新功能的列表组件作为页面一部分的可复用列表组件
stV自带生命周期管理的有状态界面需要管理状态的完整页面
stW自带生命周期管理的有状态组件需要管理状态的可复用组件
leV无状态界面纯展示、不需要状态管理的完整页面
leW无状态组件纯展示、不需要状态管理的可复用组件

深入学习

根据 MIT 许可发布。