loading_more_list

A loading more list which supports ListView,GridView,WaterfallFlow and Slivers.

pub package GitHub stars GitHub forks GitHub license GitHub issues flutter-candies

Language: English | 中文简体

Web demo for LoadingMoreList

Use

  • add library to your pubspec.yaml

dependencies:
  loading_more_list: any

  • import library in dart file

  import 'package:loading_more_list/loading_more_list.dart';
  

Prepare Data Collection

LoadingMoreBase is data collection for loading more. override loadData method to load your data. set hasMore to false when it has no more data.

class TuChongRepository extends LoadingMoreBase<TuChongItem> {
  int pageindex = 1;
  bool _hasMore = true;
  bool forceRefresh = false;
  @override
  bool get hasMore => (_hasMore && length < 30) || forceRefresh;

  @override
  Future<bool> refresh([bool clearBeforeRequest = false]) async {
    _hasMore = true;
    pageindex = 1;
    //force to refresh list when you don't want clear list before request
    //for the case, if your list already has 20 items.
    forceRefresh = !clearBeforeRequest;
    var result = await super.refresh(clearBeforeRequest);
    forceRefresh = false;
    return result;
  }

  @override
  Future<bool> loadData([bool isloadMoreAction = false]) async {
    String url = "";
    if (this.length == 0) {
      url = "https://api.tuchong.com/feed-app";
    } else {
      int lastPostId = this[this.length - 1].postId;
      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));

      var result = await HttpClientHelper.get(url);

      var source = TuChongSource.fromJson(json.decode(result.body));
      if (pageindex == 1) {
        this.clear();
      }
      for (var item in source.feedList) {
        if (item.hasImage && !this.contains(item) && hasMore) this.add(item);
      }

      _hasMore = source.feedList.length != 0;
      pageindex++;
      isSuccess = true;
    } catch (exception, stack) {
      isSuccess = false;
      print(exception);
      print(stack);
    }
    return isSuccess;
  }
}

Argument

almost of arguments are the same as official.

following arguments are made for loading more.

ListConfig and SliverListConfig

argumentdescriptiondefault
itemBuilderThe item builder of list.required
sourceListThe source list of data which extends LoadingMoreBase.required
showGlowLeadingWhether to show the overscroll glow on the side with negative scroll offsets.0.0
showGlowTrailingWhether to show the overscroll glow on the side with positive scroll offsets.-
lastChildLayoutTypeLayout type of last child(loadmore/no more item).LastChildLayoutType.foot
viewportBuilderThe builder to get indexs in viewport.-
closeToTrailingWhether make layout close to trailing.false
collectGarbageCall when collect garbage and return indexs to collect.-
gridDelegateThe delegate for GridView.-
waterfallFlowDelegateThe delegate for WaterfallFlow.-
indicatorBuilderwidget builder for different loading state.IndicatorWidget
paddingThe amount of space by which to inset the child sliver for SliverListConfig-

WaterfallFlowDelegate

argumentdescriptiondefault
crossAxisCountThe number of children in the cross axis.required
mainAxisSpacingThe number of logical pixels between each child along the main axis.0
crossAxisSpacingThe number of logical pixels between each child along the cross axis.0

Widget

LoadingMoreList

argumentdescriptiondefault
listConfigListConfigrequired
onScrollNotificationCalled when a ScrollNotification of the appropriate type arrives at this location in the tree.-

LoadingMoreSliverList

argumentdescriptiondefault
sliverListConfigSliverListConfigrequired

LoadingMoreCustomScrollView

argumentdescriptiondefault
onScrollNotificationCalled when a ScrollNotification of the appropriate type arrives at this location in the tree.-
rebuildCustomScrollViewin NestedScrollView, rebuild CustomScrollView, viewport can be computed again.false

ListView

            LoadingMoreList(
              ListConfig<TuChongItem>(
                itemBuilder: ItemBuilder.itemBuilder,
                sourceList: listSourceRepository,
                padding: EdgeInsets.all(0.0),
              ),
            ),

GridView

define GridView with gridDelegate argument.

            LoadingMoreList(
              ListConfig<TuChongItem>(
                itemBuilder: ItemBuilder.itemBuilder,
                sourceList: listSourceRepository,
                padding: EdgeInsets.all(0.0),
                gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 2,
                  crossAxisSpacing: 3.0,
                  mainAxisSpacing: 3.0,
                ),
              ),
            ),

WaterfallFlow

define WaterfallFlow with waterfallFlowDelegate argument.

            LoadingMoreList(
              ListConfig<TuChongItem>(
                waterfallFlowDelegate: WaterfallFlowDelegate(
                  crossAxisCount: 2,
                  crossAxisSpacing: 5,
                  mainAxisSpacing: 5,
                ),
                itemBuilder: _buildItem,
                sourceList: listSourceRepository,
                padding: EdgeInsets.all(5.0),
              ),
            ),

Sliver/CustomScrollView

following codes are show how to build loading more list within CustomScrollView.

      LoadingMoreCustomScrollView(
        slivers: <Widget>[
          SliverAppBar(
            pinned: true,
            title: Text("MultipleSliverDemo"),
          ),
          ///SliverList
          LoadingMoreSliverList(SliverListConfig<TuChongItem>(
            itemBuilder: ItemBuilder.itemBuilder,
            sourceList: listSourceRepository,
          )),
          SliverToBoxAdapter(
            child: Container(
              alignment: Alignment.center,
              child: Text("Next list"),
              color: Colors.blue,
              height: 100.0,
            ),
          ),
          ///SliverGrid
          LoadingMoreSliverList(
            SliverListConfig<TuChongItem>(
              itemBuilder: ItemBuilder.itemBuilder,
              sourceList: listSourceRepository1,
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 2,
                crossAxisSpacing: 3.0,
                mainAxisSpacing: 3.0,
              ),
            ),
          ),
          SliverPersistentHeader(
            delegate: CommonExtentSliverPersistentHeaderDelegate(
                Container(
                  alignment: Alignment.center,
                  child: Text("Pinned Content"),
                  color: Colors.red,
                ),
                100.0),
            pinned: true,
          ),
          ///SliverWaterfallFlow
          LoadingMoreSliverList(
            SliverListConfig<TuChongItem>(
              itemBuilder: buildWaterfallFlowItem,
              sourceList: listSourceRepository2,
              padding: EdgeInsets.symmetric(horizontal: 5.0),
              waterfallFlowDelegate: WaterfallFlowDelegate(
                crossAxisCount: 2,
                crossAxisSpacing: 5,
                mainAxisSpacing: 5,
              ),
            ),
          ),
        ],
      ),

IndicatorStatus

define loading status with indicatorBuilder argument.

        enum IndicatorStatus {
          None,
          LoadingMoreBusying,
          FullScreenBusying,
          Error,
          FullScreenError,
          NoMoreLoad,
          Empty
        }
      LoadingMoreList(
        ListConfig<TuChongItem>(
          itemBuilder: ItemBuilder.itemBuilder,
          sourceList: 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) {
    //if your list is sliver list ,you should build sliver indicator for it
    //isSliver=true, when use it in sliver list
    bool isSliver = false;

    Widget widget;
    switch (status) {
      case IndicatorStatus.None:
        widget = Container(height: 0.0);
        break;
      case IndicatorStatus.LoadingMoreBusying:
        widget = Row(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Container(
              margin: EdgeInsets.only(right: 5.0),
              height: 15.0,
              width: 15.0,
              child: getIndicator(context),
            ),
            Text("正在加载...不要着急")
          ],
        );
        widget = _setbackground(false, widget, 35.0);
        break;
      case IndicatorStatus.FullScreenBusying:
        widget = Row(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Container(
              margin: EdgeInsets.only(right: 0.0),
              height: 30.0,
              width: 30.0,
              child: getIndicator(context),
            ),
            Text("正在加载...不要着急")
          ],
        );
        widget = _setbackground(true, widget, double.infinity);
        if (isSliver) {
          widget = SliverFillRemaining(
            child: widget,
          );
        } else {
          widget = CustomScrollView(
            slivers: <Widget>[
              SliverFillRemaining(
                child: widget,
              )
            ],
          );
        }
        break;
      case IndicatorStatus.Error:
        widget = Text(
          "好像出现了问题呢?",
        );
        widget = _setbackground(false, widget, 35.0);

        widget = GestureDetector(
          onTap: () {
            listSourceRepository.errorRefresh();
          },
          child: widget,
        );

        break;
      case IndicatorStatus.FullScreenError:
        widget = Text(
          "好像出现了问题呢?",
        );
        widget = _setbackground(true, widget, double.infinity);
        widget = GestureDetector(
          onTap: () {
            listSourceRepository.errorRefresh();
          },
          child: widget,
        );
        if (isSliver) {
          widget = SliverFillRemaining(
            child: widget,
          );
        } else {
          widget = CustomScrollView(
            slivers: <Widget>[
              SliverFillRemaining(
                child: widget,
              )
            ],
          );
        }
        break;
      case IndicatorStatus.NoMoreLoad:
        widget = Text("没有更多的了。。不要拖了");
        widget = _setbackground(false, widget, 35.0);
        break;
      case IndicatorStatus.Empty:
        widget = EmptyWidget(
          "这里是空气!",
        );
        widget = _setbackground(true, widget, double.infinity);
        if (isSliver) {
          widget = SliverToBoxAdapter(
            child: widget,
          );
        } else {
          widget = CustomScrollView(
            slivers: <Widget>[
              SliverFillRemaining(
                child: widget,
              )
            ],
          );
        }
        break;
    }
    return widget;
  }

CollectGarbage

track the indexes are collect, you can collect garbage at that monment(for example Image cache)

more detail

        LoadingMoreList(
          ListConfig<TuChongItem>(
                collectGarbage: (List<int> indexes) {
                  ///collectGarbage
                  indexes.forEach((index) {
                    final item = listSourceRepository[index];
                    if (item.hasImage) {
                      final provider = ExtendedNetworkImageProvider(
                        item.imageUrl,
                      );
                      provider.evict();
                    }
                  });
                },
          ),
        ),

ViewportBuilder

track the indexes go into the viewport, it's not include cache extent.

        LoadingMoreList(
          ListConfig<TuChongItem>(
            viewportBuilder: (int firstIndex, int lastIndex) {
              print("viewport : [$firstIndex,$lastIndex]");
            },
            closeToTrailing: true
          ),
        ),

LastChildLayoutType

build lastChild as special child in the case that it is loadmore/no more item.

        enum LastChildLayoutType {
        /// as default child
        none,

        /// follow max child trailing layout offset and layout with full cross axis extend
        /// last child as loadmore item/no more item in [ExtendedGridView] and [WaterfallFlow]
        /// with full cross axis extend
        fullCrossAxisExtend,

        /// as foot at trailing and layout with full cross axis extend
        /// show no more item at trailing when children are not full of viewport
        /// if children is full of viewport, it's the same as fullCrossAxisExtend
        foot,
        }

CloseToTrailing

when reverse property of List is true, layout is as following. it likes chat list, and new session will insert to zero index. but it's not right when items are not full of viewport.

     trailing
-----------------
|               |
|               |
|     item2     |
|     item1     |
|     item0     |
-----------------
     leading

to solve it, you could set closeToTrailing to true, layout is as following. support ExtendedGridView,ExtendedList,WaterfallFlow. and it also works when reverse is flase, layout will close to trailing.

     trailing
-----------------
|     item2     |
|     item1     |
|     item0     |
|               |
|               |
-----------------
     leading
      LoadingMoreList(
        ListConfig<TuChongItem>(
          closeToTrailing: true
        ),
      ),

Libraries

loading_more_list