Flutter - How to make a widget be created when it's scrolled onto the screen

694

I found a way to do this. I was doing it well, the thing is for some reason the SliverChildBuilderDelegate always creates the first 2 elements even if they're not scrolled onto the screen. So I had to create 2 dummy invisible footers and then the real footer, this way the real footer will be created when is scrolled onto the screen:

@override
Widget build(BuildContext context) {
return Scrollable(
  controller: widget.child.controller,
  axisDirection: widget.child.getDirection(context),
  viewportBuilder: (context, offset) {
    return ShrinkWrappingViewport(
      axisDirection: widget.child.getDirection(context),
      offset: offset,
      slivers: widget.child.buildSlivers(context)
        ..add(SliverList(
            delegate: SliverChildBuilderDelegate((context, index) {
          if (index < 2) {
            print('dummy footer');
            return SizedBox.shrink();
          }
          print('footer');
          return widget.footer;
        }, childCount: 3))),
    );
  },
);

And this would be the final solution without the prints:

@override
Widget build(BuildContext context) {
  return Scrollable(
    controller: widget.child.controller,
    axisDirection: widget.child.getDirection(context),
    viewportBuilder: (context, offset) {
      return ShrinkWrappingViewport(
        axisDirection: widget.child.getDirection(context),
        offset: offset,
        slivers: widget.child.buildSlivers(context)
          ..add(SliverList(
              delegate: SliverChildBuilderDelegate(
                  (_, index) => index < 2 ? SizedBox.shrink() : widget.footer,
                  childCount: 3))),
      );
    },
  );
}

Now the custom widget adds a footer at the bottom of any scroll view efficiently, being created both the items of the scroll view and the footer only when they're scrolled onto the screen.

If someone finds a better way to do this let me know. For now, I'm okay with this solution.

Share:
694
Pablo Barrera
Author by

Pablo Barrera

A bug is never just a mistake It represents something bigger An error of thinking That makes you who you are

Updated on December 14, 2022

Comments

  • Pablo Barrera
    Pablo Barrera over 1 year

    I created a custom widget that adds a footer as last item to a received scrollable widget. Everything works fine, but I want to change the time the footer is being created.

    Like ListView.builder creates items as they're scrolled onto the screen, I want that happen with the footer too. If this custom widget receives a ListView.builder or GridView.builder, their items are being created fine as they're scrolled onto the screen, but the footer is created the first time even if it's not on the screen.

    Here is the custom widget, where the received widget is widget.child (ScrollView) and the footer is widget.footer (Widget).

    @override
    Widget build(BuildContext context) {
      return Scrollable(
        controller: widget.child.controller,
        axisDirection: widget.child.getDirection(context),
        viewportBuilder: (context, offset) {
          return ShrinkWrappingViewport(
            axisDirection: widget.child.getDirection(context),
            offset: offset,
            slivers: widget.child.buildSlivers(context)
              ..add(
                  SliverList(
                      delegate: SliverChildBuilderDelegate((context, index) {
                print('footer');
                return widget.footer;
              }, childCount: 1))),
          );
        },
      );
    }
    

    This is how I'm using it:

    ScrollableWithFooter(
      child: ListView.builder(
        itemCount: 50,
        itemBuilder: (context, index) {
          print(index);
          return SizedBox(height: 400, child: Text('Item $index'));
        },
      ),
      footer: Center(child: Text('Footer')),
    )
    

    With a list of 50, I'm seeing the first items on the screen and this is the output of the prints:

    I/flutter (28472): 0
    I/flutter (28472): 1
    I/flutter (28472): 2
    I/flutter (28472): footer
    

    And when I start scrolling, the other items start printing their indexes as they're created:

    I/flutter (28472): 2
    I/flutter (28472): footer
    I/flutter (28472): 3
    I/flutter (28472): 4
    I/flutter (28472): 5
    

    This is correct for the items but not for the footer. I want the footer to be created when I reached the end of the list, like the other items. How can achieve that?

    Note that on the screen everything works fine, the footer appears at the end of the list. It's only its creation that happens before it should.


    This is something I tried, but it doesn't work:

    @override
    Widget build(BuildContext context) {
      return Scrollable(
        controller: widget.child.controller,
        axisDirection: widget.child.getDirection(context),
        viewportBuilder: (context, offset) {
          List<Widget> slivers = widget.child.buildSlivers(context);
          return ShrinkWrappingViewport(
            axisDirection: widget.child.getDirection(context),
            offset: offset,
            slivers: [
              SliverList(
                  delegate: SliverChildBuilderDelegate((context, index) {
                if (index < slivers.length) {
                  return slivers[index]; // <- Here seems to be the problem
                }
                print('footer');
                return widget.footer;
              }, childCount: slivers.length + 1))
            ],
          );
        },
      );
    }
    

    I get this error:

    The following assertion was thrown building NotificationListener<KeepAliveNotification>:
    I/flutter (28472): A RenderRepaintBoundary expected a child of type RenderBox but received a child of type
    I/flutter (28472): RenderSliverPadding.
    
    • user3718908x100
      user3718908x100 over 4 years
      Why don't you just insert an empty string or a null as the last item in your list. Inside your widget (the one you use for each item in the list) check if the list is at the last index and if the last index is empty or null. If it is render your footer. I think that should resolve your issue, tried something like that before.
    • Pablo Barrera
      Pablo Barrera over 4 years
      How can I do that? I'm receiving a ScrollView widget, I can't modify its list.
    • Arash Mohammadi
      Arash Mohammadi over 4 years
      What if you use animations? make the footer, scale it to zero with a animation controller, after you scroll to bottom, use the animation controller to scale it back. Try it, might work.
  • Pablo Barrera
    Pablo Barrera over 4 years
    The problem with that approach is if I scroll to the end the scroll stops (because there's no footer), then I call setState() to show the footer, and then I have to manually scroll again to see the footer.