美文网首页Flutter圈子Flutter中文社区Flutter
Flutter 轻松构建加载更多(loading more)

Flutter 轻松构建加载更多(loading more)

作者: 法的空间 | 来源:发表于2019-02-19 22:27 被阅读37次

    在我软的UWP里面有一个接口ISupportIncrementalLoading
    只要你的集合继承这个,并且实现里面的方法,就能自动实现加载更多的这个动作。说白了就是UWP里面UI列表控件跟集合一个契约。

    在Flutter里面没有这种类似的东西,但是实际项目里面会出现大量的列表需要加载更多。


    image

    FlutterCandies QQ群:181398081
    不哭乖站起来继续撸代码,Flutter bug builder 马上上代码。

    无图无真相,先上一个图。

    image
    首先我们也来定义个LoadingMoreBase 契约类
    class LoadingMoreBase<T> extends ListBase<T>
        with _LoadingMoreBloc<T>, RefreshBase {
      var _array = <T>[];
    
      @override
      T operator [](int index) {
        // TODO: implement []
        return _array[index];
      }
    
      @override
      void operator []=(int index, T value) {
        // TODO: implement []=
        _array[index] = value;
      }
    
      bool get hasMore => true;
      bool isLoading = false;
    
      IndicatorStatus indicatorStatus = IndicatorStatus.None;
    
      Future<bool> loadMore() async {
        if (isLoading || !hasMore) return true;
        // TODO: implement loadMore
    
        var preStatus = indicatorStatus;
        indicatorStatus = this.length == 0
            ? IndicatorStatus.FullScreenBusying
            : IndicatorStatus.LoadingMoreBusying;
    
        if (preStatus == IndicatorStatus.Error) {
          onStateChanged(this);
        }
        isLoading = true;
        var isSuccess = await loadData();
        isLoading = false;
        if (isSuccess) {
          if (this.length == 0) indicatorStatus = IndicatorStatus.Empty;
        } else {
          indicatorStatus = IndicatorStatus.Error;
        }
        onStateChanged(this);
        return isSuccess;
      }
    
      Future<bool> loadData() async {
        return true;
      }
    
      @override
      Future<bool> onRefresh() async {
        // TODO: implement OnRefresh
      }
    
      @override
      int get length => _array.length;
      set length(int newLength) => _array.length = newLength;
    
      @override
      void onStateChanged(LoadingMoreBase<T> source) {
        // TODO: implement notice
        super.onStateChanged(source);
      }
    }
    
    class _LoadingMoreBloc<T> {
      final _rebuild = new StreamController<LoadingMoreBase<T>>.broadcast();
      Stream<LoadingMoreBase<T>> get rebuild => _rebuild.stream;
    
      void onStateChanged(LoadingMoreBase<T> source) {
        if (!_rebuild?.isClosed) _rebuild.sink.add(source);
      }
    
      void dispose() {
        _rebuild?.close();
      }
    }
    

    继承于ListBase<T> 方便后面继承

    3个重要的方法:
    用于加载更多

     Future<bool> loadMore() async
    

    用于刷新(重置列表)

     Future<bool> onRefresh() async 
    

    用于获取数据,loadmore会调用这个方法,一般我们override的这个方法,loadmore里面有一些状态控制,如果你需要overrdie loadmore方法,注意查看下之前里面的状态控制代码

     Future<bool> loadData() async
    

    3个重要的属性:
    hasMore 判断是否还有更多
    isLoading 判断是否正在获取数据
    indicatorStatus 判断当前列表的状态

    _LoadingMoreBloc 可以通过这个类来通知streambuilder更新UI

    下面是如何继承使用这个base 类

    class TuChongRepository extends LoadingMoreBase<TuChongItem> {
      int pageindex = 1;
    
      @override
      // TODO: implement hasMore
      bool _hasMore = true;
      bool get hasMore => _hasMore && length < 20;
    
      @override
      Future<bool> onRefresh() async {
        // TODO: implement onRefresh
        pageindex = 1;
        return loadMore();
      }
    
      @override
      Future<bool> loadData() async {
        // TODO: implement getData
        String url = "";
        if (this.length == 0) {
          url = "https://api.tuchong.com/feed-app";
        } else {
          int lastPostId = this[this.length - 1].post_id;
          url =
              "https://api.tuchong.com/feed-app?post_id=${lastPostId}&page=${pageindex}&type=loadmore";
        }
        bool isSuccess = false;
        try {
          //to show loading more clearly, in your app,remove this
          await Future.delayed(Duration(milliseconds: 500, seconds: 1));
    
          var result = await HttpFactory.getInstance().getHttpClient().get(url);
    
          var source = TuChongSource.fromJson(json.decode(result.body));
          if (pageindex == 1) {
            this.clear();
          }
    
          source.feedList.forEach((item) {
            if (item.hasImage && !this.contains(item) && hasMore) {
              this.add(item);
            }
          });
    
          _hasMore = source.feedList.length != 0;
          pageindex++;
          isSuccess = true;
        } catch (exception) {
          isSuccess = false;
          print(exception);
        }
        return isSuccess;
      }
    }
    

    将你请求列表的代码加到getData方法里面,这样数据源的准备就好了。

    下面说说UI组件
    这一部分分为ListView/GridView 和SliverList/SliverGrid

    ListView/GridView

    LoadingMoreList 里面的部分代码,StreamBuilder为更新UI,NotificationListener为了监听滑动状态

    class LoadingMoreList<T> extends StatelessWidget {
      final ListConfig<T> listConfig;
    
      LoadingMoreList(this.listConfig,{Key key})
          : super(key: key);
      @override
      Widget build(BuildContext context) {
        return StreamBuilder<LoadingMoreBase>(
          builder: (d, s) {
            return NotificationListener<ScrollNotification>(
              //key: _key,
              onNotification: _handleScrollNotification,
              child: NotificationListener<OverscrollIndicatorNotification>(
                  onNotification: _handleGlowNotification,
                  child: listConfig.buildContent(context, s.data)),
            );
          },
          stream: listConfig.sourceList?.rebuild,
        );
      }
    }
    

    ListConfig 里面提供了ListView/GridView的全部参数,这里我也提供了去掉滚动越界效果(就是列表滚不动的时候出现的水波纹效果)的2个属性showGlowLeading/showGlowTrailing。

    final Axis scrollDirection;
      final bool reverse;
      final ScrollController controller;
      final bool primary;
      final ScrollPhysics physics;
      final bool shrinkWrap;
      final EdgeInsetsGeometry padding;
      final double itemExtent;
      final int itemCount;
      final bool addAutomaticKeepAlives;
      final bool addRepaintBoundaries;
      final bool addSemanticIndexes;
      final double cacheExtent;
      final int semanticChildCount;
    
      /// Whether to show the overscroll glow on the side with negative scroll
      /// offsets.
      final bool showGlowLeading;
    
      /// Whether to show the overscroll glow on the side with positive scroll
      /// offsets.
      final bool showGlowTrailing;
    
      ListConfig(
        @required itemBuilder,
        @required sourceList, {
        this.showGlowLeading: true,
        this.showGlowTrailing: true,
        LoadingMoreIndicatorBuilder indicatorBuilder,
        SliverGridDelegate gridDelegate,
        this.scrollDirection = Axis.vertical,
        this.reverse = false,
        this.controller,
        this.primary,
        this.physics,
        this.shrinkWrap = false,
        this.padding,
        this.itemExtent,
        this.itemCount,
        this.addAutomaticKeepAlives = true,
        this.addRepaintBoundaries = true,
        this.addSemanticIndexes = true,
        this.cacheExtent,
        this.semanticChildCount,
      }) : super(itemBuilder, sourceList,
                indicatorBuilder: indicatorBuilder, gridDelegate: gridDelegate);
    

    sourceList 就是之前我们完成的loadingmore 数据源
    itemBuilder 是每个item长什么样子

    Demo code

    class ListViewDemo extends StatefulWidget {
      @override
      _ListViewDemoState createState() => _ListViewDemoState();
    }
    
    class _ListViewDemoState extends State<ListViewDemo> {
      TuChongRepository listSourceRepository;
      @override
      void initState() {
        // TODO: implement initState
        listSourceRepository = new TuChongRepository();
        super.initState();
      }
    
      @override
      void dispose() {
        listSourceRepository?.dispose();
        // TODO: implement dispose
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Material(
          child: Column(
            children: <Widget>[
              AppBar(
                title: Text("ListViewDemo"),
              ),
              Expanded(
                child: LoadingMoreList(
                    ListConfig<TuChongItem>(
                        ItemBuilder.itemBuilder, listSourceRepository,
    //                    showGlowLeading: false,
    //                    showGlowTrailing: false,
                        padding: EdgeInsets.all(0.0)),),
              )
            ],
          ),
        );
      }
    }
    

    这样子实现了一个加载更多的ListView,如果是GridView的话请给gridDelegate赋值.

    SliverList/SliverGrid

    image
    支持多个loadmore列表
    SliverListConfig 里面包含了SliverList/SliverGrid里面的参数
    //config for SliverList and SliverGrid
    class SliverListConfig<T> extends LoadingMoreListConfig<T> {
      //whether show no more  .
      bool showNoMore = true;
      //whether show fullscreenLoading for multiple sliver
      bool showFullScreenLoading = true;
    
      final bool addAutomaticKeepAlives;
      final bool addRepaintBoundaries;
      final bool addSemanticIndexes;
      final SemanticIndexCallback semanticIndexCallback;
      final int semanticIndexOffset;
      final int childCount;
    
      SliverListConfig(
        @required itemBuilder,
        @required sourceList, {
        LoadingMoreIndicatorBuilder indicatorBuilder,
        SliverGridDelegate gridDelegate,
        this.addAutomaticKeepAlives = true,
        this.addRepaintBoundaries = true,
        this.addSemanticIndexes = true,
        this.semanticIndexCallback = _kDefaultSemanticIndexCallback,
        this.semanticIndexOffset = 0,
        this.childCount,
      }) : super(itemBuilder, sourceList,
                indicatorBuilder: indicatorBuilder, gridDelegate: gridDelegate);
    

    LoadingMoreCustomScrollView使用来创建Sliver组件,它包括了CustomScrollView的属性以及showGlowLeading/showGlowTrailing

    //support for LoadingMoreSliverList
    class LoadingMoreCustomScrollView extends StatefulWidget {
      final List<Widget> slivers;
      final Axis scrollDirection;
      final bool reverse;
      final ScrollController controller;
      final bool primary;
      final ScrollPhysics physics;
      final bool shrinkWrap;
      final double cacheExtent;
      final int semanticChildCount;
    
      /// Whether to show the overscroll glow on the side with negative scroll
      /// offsets.
      final bool showGlowLeading;
    
      /// Whether to show the overscroll glow on the side with positive scroll
      /// offsets.
      final bool showGlowTrailing;
    
      LoadingMoreCustomScrollView({
        Key key,
        this.scrollDirection = Axis.vertical,
        this.reverse = false,
        this.controller,
        this.primary,
        this.physics,
        this.shrinkWrap = false,
        this.cacheExtent,
        this.slivers = const <Widget>[],
        this.semanticChildCount,
        this.showGlowLeading: true,
        this.showGlowTrailing: true,
      })  : assert(slivers != null),
            super(key: key);
    

    Demo code

    简单的一个Sliver

    class SliverListDemo extends StatefulWidget {
      @override
      _SliverListDemoState createState() => _SliverListDemoState();
    }
    
    class _SliverListDemoState extends State<SliverListDemo> {
      TuChongRepository listSourceRepository;
      @override
      void initState() {
        // TODO: implement initState
        listSourceRepository = new TuChongRepository();
        super.initState();
      }
    
      @override
      void dispose() {
        listSourceRepository?.dispose();
        // TODO: implement dispose
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
    
        return Material(
          child: LoadingMoreCustomScrollView(
            slivers: <Widget>[
              SliverAppBar(
                pinned: true,
                title: Text("SliverListDemo"),
              ),
              LoadingMoreSliverList(
                  SliverListConfig<TuChongItem>(
                    ItemBuilder.itemBuilder, listSourceRepository,
                    //isLastOne: false
                  ))
            ],
          ),
        );
      }
    }
    
    

    多个Sliver

    class MultipleSliverDemo extends StatefulWidget {
      @override
      _MultipleSliverDemoState createState() => _MultipleSliverDemoState();
    }
    
    class _MultipleSliverDemoState extends State<MultipleSliverDemo> {
      TuChongRepository listSourceRepository;
      TuChongRepository listSourceRepository1;
    
      @override
      void initState() {
        // TODO: implement initState
        listSourceRepository = new TuChongRepository();
        listSourceRepository1 = new TuChongRepository();
        super.initState();
      }
    
      @override
      void dispose() {
        listSourceRepository?.dispose();
        listSourceRepository1?.dispose();
        // TODO: implement dispose
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Material(
          child: LoadingMoreCustomScrollView(
            slivers: <Widget>[
              SliverAppBar(
                pinned: true,
                title: Text("MultipleSliverDemo"),
              ),
              LoadingMoreSliverList(SliverListConfig<TuChongItem>(
                ItemBuilder.itemBuilder,
                listSourceRepository,
              )),
              SliverToBoxAdapter(
                child: Container(
                  alignment: Alignment.center,
                  child: Text("Next list"),
                  color: Colors.blue,
                  height: 100.0,
                ),
              ),
              SliverPersistentHeader(
                delegate: CommonSliverPersistentHeaderDelegate(
                    Container(
                      alignment: Alignment.center,
                      child: Text("Pinned Content"),
                      color: Colors.red,
                    ),
                    100.0),
                pinned: true,
              ),
              LoadingMoreSliverList(SliverListConfig<TuChongItem>(
                ItemBuilder.itemBuilder,
                listSourceRepository1,
                gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 2,
                  crossAxisSpacing: 3.0,
                  mainAxisSpacing: 3.0,
    //                    childAspectRatio: 0.5
                ),
              ))
            ],
          ),
        );
      }
    }
    

    那么怎么自定义这些状态的显示内容呢?
    我在config里面提供了indicatorBuilder,你可以根据当前list的状态自定义显示效果

    image

    demo code

     @override
      Widget build(BuildContext context) {
        return Material(
          child: Column(
            children: <Widget>[
              AppBar(
                title: Text("CustomIndicatorDemo"),
              ),
              Expanded(
                child: LoadingMoreList(
                  ListConfig<TuChongItem>(
                      ItemBuilder.itemBuilder, listSourceRepository,
                      indicatorBuilder: _buildIndicator,
                      padding: EdgeInsets.all(0.0),
                  ),
                ),
              )
            ],
          ),
        );
      }
    
      //you can use IndicatorWidget or build yourself widget
      //in this demo, we define all status.
      Widget _buildIndicator(BuildContext context, IndicatorStatus status) {
        Widget widget;
        bool full = (status == IndicatorStatus.FullScreenBusying);
        double height = 35.0;
        switch (status) {
          case IndicatorStatus.None:
            widget = Container(height: 0.0);
            height = 0.0;
            break;
          case IndicatorStatus.LoadingMoreBusying:
          case IndicatorStatus.FullScreenBusying:
            double indicatorSize = full ? 30.0 : 15.0;
            widget = Row(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                Container(
                    margin: EdgeInsets.only(right: 15.0),
                    height: indicatorSize,
                    width: indicatorSize,
                    child: getIndicator(context)),
                (!full
                    ? Text(
                        "正在加载...慌什么慌",
                      )
                    : Text("正在加载...慌什么慌",
                        style: TextStyle(
                            fontWeight: FontWeight.bold, fontSize: 28.0))),
              ],
            );
            break;
          case IndicatorStatus.Error:
            widget = Text(
              "加载失败,搞个川川",
            );
            break;
          case IndicatorStatus.NoMoreLoad:
            widget = Text("没有了,不要拖了");
            break;
          case IndicatorStatus.Empty:
            widget = EmptyWidget(
              "这里只有空",
            );
            break;
        }
    
        widget = Container(
            width: double.infinity,
            height: full ? double.infinity : height,
            child: widget,
            color: Colors.grey[200],
            alignment: Alignment.center);
    
    //    if (isSliver) {
    //      if (status == IndicatorStatus.FullScreenBusying) {
    //        widget = SliverFillRemaining(
    //          child: widget,
    //        );
    //      } else if (status == IndicatorStatus.Empty) {
    //        widget = SliverToBoxAdapter(
    //          child: widget,
    //        );
    //      }
    //    }
        if (status == IndicatorStatus.Error) {
          widget = GestureDetector(
            onTap: () {
              listSourceRepository.loadMore();
            },
            child: widget,
          );
        }
        return widget;
      }
    }
    

    最后放上 Github loading_more_list,如果你想要其他效果或者有什么不明白的地方,都请告诉我。

    自此,妈妈再也不会担心我不会处理Flutter的列表了。Fluter 花样下拉刷新 + Flutter 轻松构建加载更多(loading more)
    5分钟上手Flutter列表

    image

    相关文章

      网友评论

        本文标题:Flutter 轻松构建加载更多(loading more)

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