renefloor / side_header_list_view

Listview with sticky headers like the Android contact page

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Add infinte scroll loading data

toregua opened this issue · comments

Hi renefloor,
It is the first time i want to contributate on github and i don't find how to contribute.
I tried pull-request but it redirect me to comparing code.

So i have made my change in the code and i give you here the new feature :

side_header_list_view.dart

library side_header_list_view;

import 'package:flutter/material.dart';
import 'package:meta/meta.dart';

/**
 *  SideHeaderListView for Flutter
 *
 *  Copyright (c) 2017 Rene Floor
 *
 *  Released under BSD License.
 */

typedef bool HasSameHeader(int a, int b);
typedef void LoadMore();

class SideHeaderListView extends StatefulWidget {
  final int itemCount;
  final IndexedWidgetBuilder headerBuilder;
  final IndexedWidgetBuilder itemBuilder;
  final EdgeInsets padding;
  final HasSameHeader hasSameHeader;
  final LoadMore loadMore;
  final itemExtend;

  SideHeaderListView({
    Key key,
    this.itemCount,
    @required this.itemExtend,
    @required this.headerBuilder,
    @required this.itemBuilder,
    @required this.hasSameHeader,
    this.padding,
    this.loadMore,
  })
      : super(key: key);

  @override
  _SideHeaderListViewState createState() => new _SideHeaderListViewState();
}

class _SideHeaderListViewState extends State<SideHeaderListView> {
  int currentPosition = 0;

  @override
  Widget build(BuildContext context) {
    return new Stack(
      children: <Widget>[
        new Positioned(
          child: new Opacity(
            opacity: _shouldShowHeader(currentPosition) ? 0.0 : 1.0,
            child: widget.headerBuilder(
                context, currentPosition >= 0 ? currentPosition : 0),
          ),
          top: 0.0,
          left: 0.0,
        ),
        new ListView.builder(
            padding: widget.padding,
            itemCount: widget.itemCount,
            itemExtent: widget.itemExtend,
            controller: _getScrollController(),
            itemBuilder: (BuildContext context, int index) {
              return new Row(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  new FittedBox(
                    child: new Opacity(
                      opacity: _shouldShowHeader(index) ? 1.0 : 0.0,
                      child: widget.headerBuilder(context, index),
                    ),
                  ),
                  new Expanded(child: widget.itemBuilder(context, index))
                ],
              );
            }),
      ],
    );
  }

  bool _shouldShowHeader(int position) {
    if (position < 0) {
      return true;
    }
    if (position == 0 && currentPosition < 0) {
      return true;
    }

    if (position != 0 &&
        position != currentPosition &&
        !widget.hasSameHeader(position, position - 1)) {
      return true;
    }

    if (position != widget.itemCount - 1 &&
        !widget.hasSameHeader(position, position + 1) &&
        position == currentPosition) {
      return true;
    }
    return false;
  }

  ScrollController _getScrollController() {
    var controller = new ScrollController();
    controller.addListener(() {
      var pixels = controller.offset;
      var newPosition = (pixels / widget.itemExtend).floor();

      if (newPosition != currentPosition) {
        setState(() {
          currentPosition = newPosition;
        });
      }
    });
    if (widget.loadMore != null) {
      controller.addListener(() {
        var pixels = controller.offset;
        var bottom = controller.position.maxScrollExtent;
        if (pixels == bottom) {
          widget.loadMore();
        }
      });
    }
    return controller;
  }
}

CHANGELOG.md

[0.0.3] - 2017-12-27

Add LoadMore function in order to add inifinte scroll. When user arrives at the bottom of the screen, loadmore is call.

[0.0.2] - 2017-12-27

Bug fix release with two bug fixes:

  • Overscroll on iOS resulted in an out of bounds exception
  • Last item never showed a header, although it should have.

[0.0.1] - 2017-12-09

  • First release of this awesome project :)

Hope you will add this feature 👍

Toregua

Hi @toregua
Thanks for the code. Can you also add some sample?
I assume you rebuild the widget (with setState) after you have loaded more items? Does the scroll position stays the same after you load? Do you have a progress indicator as last item?

I would add a check that it only calls 'LoadMore' once for a specific ScrollExtend. Otherwise it keeps calling loadMore when you scroll and I think it is better to catch that here than in 'LoadMore'.

Edit: I just thought it might be better to do it differently.
Infinite scroll is quite a specific feature and there are probably more features like that.
I am going to have a look at this, but I think it should be possible to catch a ScrollNotification in the Widget that uses the SideHeaderListView.
When you can get this in the outer widget all logic can be implemented on that level.

I just had a look and it works fine in the following way:

      return new NotificationListener<UserScrollNotification>(
        onNotification: onScrollNotification,
        child: new SideHeaderListView(
          itemCount: items.length,
          itemExtend: 150.0,
          headerBuilder: (BuildContext context, int index){
            return new DayWidget(items[index].startDate);
          },
          itemBuilder: (BuildContext context, int index){
            return new BattleListItem(items[index], key, index);
          },
          hasSameHeader: (int a, int b){
            return items[a].startDate == items[b].startDate;
          },
        ),
      );

with

  onScrollNotification(UserScrollNotification notification){
    debugPrint("Scroll: ${notification.metrics.extentBefore}/${notification.metrics.maxScrollExtent}");
  }

You can see all types of scrollnotifications here: https://docs.flutter.io/flutter/widgets/ScrollNotification-class.html
So for example you have ScrollStart, ScrollUpdate and ScrollEnd.

I can also imagine that you might want to load new items when the user still has some items left to scroll so it becomes more smooth.

I might add this feature later, but I don't think so. I suggest to use the method with the notifications.

These notifications are given by everything that scrolls, so you could also make a package that handles this with any type of scrollview.

Hi renefloor, you are right your solution is widely better because it totally decoupled from the component.
I juste tried your solution and it work perfectly.
Many thanks

Good luck with your project.
I would love to see the result. Do you plan to publish something in an app store or on github?