美文网首页
分页列表父类-快速搭建页面

分页列表父类-快速搭建页面

作者: 小城哇哇 | 来源:发表于2023-05-14 19:56 被阅读0次

    每次复制一份代码

    看着重复的代码发呆

    是时候拿起重构的武器了

    前言

    编写了一个PagingListWidget组件,提取了分页列表通用逻辑,其他业务相关的采用虚方法,由子类来实现。

    通用逻辑包括:(1)下拉刷新。(2)上拉加载。(3)空数据展示。

    其他业务逻辑:(1)导航栏信息。(2)获取列表信息。(3)列表项的视图。

    使用方法,直接继承PagingListWidget并实现虚方法。

    一、 起因

    作为业务开发工程师,每次新需求来,看到都是列表展示,毫无技术难度。直接ctrl + c 然后ctrl + v,一顿接口修改,开发完成。

    一直觉得这已经挺良心了。可突然腻了。

    想着是不是可以再懒一点,想多点自由时间来研究怎么能够更懒。

    二、 提取

    暴力开启,直接拷贝了一个列表页面的代码,然后将业务相关代码删除,仅仅留下通用的逻辑代码。如下:

    
    /// 分页列表的页面
    /// navigationTitle返回标题,页面展示一个title,一个列表。
    /// navigationTitle返回null。页面仅仅为一个列表。此时可以重载build添加其他widgets。
    
    abstract class PagingListWidget extends StatefulWidget {
      const PagingListWidget({Key? key}) : super(key: key);
    
      @override
      PagingListWidgetState createState();
    }
    
    abstract class PagingListWidgetState<T extends PagingListWidget, S> extends State<T> {
      // 实现类 获取数据时使用
      final int pageSize = 20;
      int page = 1;
      final List<S> dataList = [];
      bool showLoadingMore = false;
      int total = 0;
      bool hasInitialed = false;
    
      bool _disposed = false;
      final ScrollController _scrollController = ScrollController();
      final GlobalKey<RefreshIndicatorState> _refreshKey = GlobalKey();
    
      @override
      void initState() {
        super.initState();
    
        _scrollController.addListener(() {
          if (showLoadingMore) {
            return;
          }
          if (_scrollController.position.pixels > (_scrollController.position.maxScrollExtent - 20) && total > dataList.length) {
            setState(() {
              showLoadingMore = true;
            });
            _loadMore();
          }
        });
    
        Future.delayed(const Duration(seconds: 0), () {
          _onRefresh();
        });
      }
    
      @override
      void dispose() {
        _disposed = true;
    
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        var title = navigationTitle();
        if (null == title) {
          return _contentWidget();
        }
    
        return Scaffold(
          appBar: AppBar(
            title: Text(title),
          ),
          body: _contentWidget(),
        );
      }
    
    // Widget __START__
    
      Widget _contentWidget() {
        return SafeArea(
          child: !hasInitialed
              ? const MyCircularProgress()
              : RefreshIndicator(
                  key: _refreshKey,
                  onRefresh: _onRefresh,
                  child: dataList.isEmpty
                      ? const NoData()
                      : ListView.builder(
                          itemBuilder: (context, index) {
                            if (index == dataList.length) {
                              return showLoadingMore
                                  ? Container(
                                      margin: const EdgeInsets.all(10),
                                      child: const Align(
                                        child: CircularProgressIndicator(
                                          color: MyColors.mainColor,
                                        ),
                                      ),
                                    )
                                  : total <= dataList.length
                                      ? Container(
                                          margin: const EdgeInsets.all(10),
                                          alignment: Alignment.center,
                                          child: const Text("没有更多数据"),
                                        )
                                      : const SizedBox(
                                          height: 44,
                                        );
                            }
                            return listItem(index);
                          },
                          physics: const AlwaysScrollableScrollPhysics(),
                          itemCount: dataList.length + 1,
                          controller: _scrollController,
                        ),
                ),
        );
      }
    
    // Widget __END__
    
    // Network __START__
      Future<void> _onRefresh() async {
        dataList.clear();
    
        page = 1;
    
        if (_disposed) {
          return;
        }
    
        fetchData();
      }
    
      Future<void> _loadMore() async {
        page++;
    
        if (_disposed) {
          return;
        }
    
        fetchData();
      }
    
    // Network __END__
    
      // 虚方法 __START__
    
      // 标题
      // 返回为null 则说明此页面不要包含导航栏,仅仅返回列表页面。
      // 返回标题时,那么直接展示完整的页面
      String? navigationTitle();
    
      // 获取列表的数据。
      Future<void> fetchData();
    
      // 列表视图
      Widget listItem(int index);
    
    // 虚方法 __END__
    }
    
    

    三、 思路

    1. 为了使用尽量方便,直接继承StatefulWidgetConsumerStatefulWidget
    2. 将所有的值都放在父类,子类通过super来访问。
    3. 列表信息的data model,采用泛型传入。abstract class PagingListWidgetState<T extends PagingListWidget, S>避免类型强转。
    4. 提高复用性。根据navigationTitle来判断是否是带导航栏。
    5. 提供自定义的部分,采用虚方法,让子类来实现。

    四、样例

    将原来的页面采用PagingListWidget重写下,代码量减少了一半。并且以后编写分页列表的新页面,开发时间减少90%吧。爽了。

    举一个例子,如下:

    class MyList extends PagingListWidget {
      const MyList({super.key});
    
      @override
      PagingListWidgetState<PagingListWidget, dynamic> createState() => _MyListState();
    }
    
    class _MyListState extends PagingListWidgetState<MyList, String> {
      var random = Random();
    
      @override
      Future<void> fetchData() async {
        return Future.delayed(const Duration(seconds: 3), () {
          super.total = 100000;
          super.dataList.addAll(List.generate(super.pageSize, (index) => "测试下 ${random.nextInt(10000000)}"));
    
          setState(() {
            super.showLoadingMore = false;
            super.hasInitialed = true;
          });
        });
      }
    
      @override
      Widget listItem(int index) {
        return Container(
          color: MyColors.randomColor(),
          height: 44,
          padding: const EdgeInsets.only(top: 8, left: 8),
          child: Text(super.dataList[index], style: TextStyle(color: MyColors.randomColor(), fontSize: 16),),
        );
      }
    
      @override
      String? navigationTitle() {
        return "测试分页列表";
      }
    }
    
    
    1. 创建子类。
    2. 实现虚方法。
    3. 完成

    五、结尾

    思路突然打开了。重复的代码以后就用这个方式重构下,代码量和开发量下降好多。

    抛砖引玉。请赐教更好的思路。

    相关文章

      网友评论

          本文标题:分页列表父类-快速搭建页面

          本文链接:https://www.haomeiwen.com/subject/gtwpsdtx.html