skip to Main Content

I have a code that has these main widgets:

  CustomScrollView(
    physics: const AlwaysScrollableScrollPhysics(
      parent: BouncingScrollPhysics(),
    ),
    slivers: [
      SliverFillRemaining(
        hasScrollBody: true,
        child: ListView(
          children: [],
        ),
      ),
    ],
  ),

I have it this way because around the ListView widget I have a Column so that on top of I have a widget that simulates a title.

I chose to work with all of them this way so that when my list has 2-3 items, the entire list and title show on the centre of the screen, and the outer scroll is bouncing with the title.

When the list is longer, what I wanted to accomplish was almost what I got, but I want to know if I’m able to control the scrolling with these rules:

  1. Scroll the list;
  2. If the list has ended (either top or bottom), scroll the CustomScrollView

2

Answers


  1. You can use sliver_tools package to pin the title only for an amount of scroll. I created an example by modifying this medium tutorial. The example will seem like https://imgur.com/a/W3nND7x

    import 'package:flutter/material.dart';
    import 'package:sliver_tools/sliver_tools.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          theme: ThemeData(
            colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
          ),
          home: const MyHomePage(),
        );
      }
    }
    
    class MyHomePage extends StatelessWidget {
      const MyHomePage({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: SafeArea(
            child: Center( // center content if shrinkWrap is true
              child: CustomScrollView(
                shrinkWrap: true, // Important for center content when it is smaller that screen height
                physics: const AlwaysScrollableScrollPhysics(
                  parent: BouncingScrollPhysics(),
                ),
                slivers: [
                  Section( // use custom 'Section' class with title property for semi-pinned header
                    title: 'Semi-Pinned header',
                    items: List.generate(20, (index) => ListTile(
                      title: Text('Item with pinned header $index'),
                    )),
                  ),
                  Section( // use custom 'Section' class without title property for only content.
                    items: List.generate(20, (index) => ListTile(
                      title: Text('Item without any header $index'),
                    )),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }
    
    class Section extends MultiSliver {
      Section({
        Key? key,
        String? title,
        Color headerColor = Colors.blue,
        Color titleColor = Colors.black,
        required List<Widget> items,
      }) : super(
              key: key,
              pushPinnedChildren: true,
              children: [
                if (title != null) SliverPinnedHeader(
                  child: ColoredBox(
                    color: headerColor,
                    child: ListTile(
                      textColor: titleColor,
                      title: Text(title),
                    ),
                  ),
                ),
                SliverList(
                  delegate: SliverChildListDelegate.fixed(items),
                ),
              ],
            );
    }
    
    Login or Signup to reply.
  2. I haven’t delved that deep into scrolling yet, so i can only tell you what my approach would be. And i hope i understand your wanted behaviour correctly.

    Similar to your example snippet use a CustomScrollView with SliverFixedExtentList for the parts of your outer list.

    If you now want an inner list in the middle that is scrollable as well, use SliverToBoxAdapter with SizedBox.

    And if you want an inner scrollable list at the end that is expanded to the remaining screen space, then use SliverFillRemaining.

    To now scroll the outer list if the inner lists reach the scroll end, use a ScrollController with OverscrollNotification.

    And then you have inner scrollable lists that scroll the outer list when they reach the end of their own scroll:

    Widget _build(BuildContext context) {
      final ScrollController outerController = ScrollController(); // todo: this scroll controller should be created
      // inside of your state object instead!
    
      return CustomScrollView(
        controller: outerController,
        slivers: <Widget>[
          SliverFixedExtentList(
            itemExtent: 100,
            delegate: SliverChildBuilderDelegate(
              (BuildContext context, int index) => Container(color: Colors.red[(index % 4) * 200 + 200], height: 100),
              childCount: 10,
            ),
          ),
          SliverToBoxAdapter(
            child: SizedBox(
              height: 300,
              child: NotificationListener<OverscrollNotification>(
                child: ListView.builder(
                  itemBuilder: (BuildContext context, int index) =>
                      Container(color: Colors.green[(index % 4) * 200 + 200], height: 100),
                  itemCount: 10,
                ),
                onNotification: (OverscrollNotification notification) {
                  final double newOffset = outerController.offset + notification.overscroll;
                  outerController.jumpTo(newOffset);
                  return true;
                },
              ),
            ),
          ),
          SliverFillRemaining(
            hasScrollBody: true,
            child: NotificationListener<OverscrollNotification>(
              child: ListView.builder(
                itemBuilder: (BuildContext context, int index) =>
                    Container(color: Colors.blue[(index % 4) * 200 + 200], height: 100),
                itemCount: 10,
              ),
              onNotification: (OverscrollNotification notification) {
                final double newOffset = outerController.offset + notification.overscroll;
                if (newOffset < outerController.position.maxScrollExtent &&
                    newOffset > outerController.position.minScrollExtent) {
                  // todo: this if condition prevents bouncy scrolling which is a bit weird without better physics
                  // calculations
                  outerController.jumpTo(newOffset);
                }
                return true;
              },
            ),
          ),
        ],
      );
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search