Thanks to visit codestin.com
Credit goes to github.com

Skip to content

WIP - Allowing scrolling content to inform insets #107182

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 14 commits into from

Conversation

Piinks
Copy link
Contributor

@Piinks Piinks commented Jul 6, 2022

This is a WIP, and primarily for full-picture reference.
Actually landing this will be split up into multiple PRs.


Issues:
Fixes #107322

Design Doc


This allows scrolling content to contribute inset information to inform things like overscroll indicators and scrollbars.

Widgets that contribute inset information:

  • SliverAppBar
  • SliverPersistentHeader
  • CupertinoSliverNavigationBar
  • SliverOverlapAbsorber/Injector (might be redundant, checking test cases with NSV on this one)

Widgets that use this inset information:

  • Scrollbar
  • RefreshIndicator
  • GlowingOverscrollIndicator
  • StretchingOverscrollIndicator
  • MaterialBanner

Status: Still need to confirm a few test cases (on separate branches)

  • NestedScrollView
  • CupertinoSliverNavigationBar
  • StretchingOverscrollIndicator

There is a scrollbar cleanup that needs to land first, and a couple of other bugs I found in the course of working on this.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Copy link
Contributor Author

@Piinks Piinks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xu-baolin would you mind taking a peek at the scrollbar part of this when you have a chance?
I made all of those scrollbar bug fixes and cleanup PRs while working on this, and I might be missing another bug. Or maybe this idea won't work. :(

This allows the scrollbar to respect things like SliverAppBar, no longer overlapping it. It mostly works, but sometimes it will get out of sync and overlap. I can't quite figure it out, I may have been looking at it too long. 🙄

See what you think of the various SliverAppBar settings:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: RawScrollbar(
          thumbVisibility: true,
          thickness: 20,
          thumbColor: Colors.red,
          child: CustomScrollView(
            primary: true,
            slivers: [
              const SliverAppBar(
                floating: true,
                // pinned: true,
                // snap: true,
                // stretch: true,
              ),
              SliverList(
                delegate: SliverChildBuilderDelegate(
                  (context, index) => Text('Item $index'),
                  childCount: 200,
                )
              ),
            ],
          ),
        )
      ),
    );
  }
}

@@ -161,6 +161,7 @@ class RefreshIndicator extends StatefulWidget {
/// value is calculated from that offset instead of the parent's edge.
final double displacement;

/// TODO(Piinks): Update docs
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also todo, add ScrollMetricsNotification here for initialization.

@@ -236,7 +236,7 @@ class _GlowingOverscrollIndicatorState extends State<GlowingOverscrollIndicator>
confirmationNotification.dispatch(context);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also todo, add ScrollMetricsNotification here for initialization.

@Piinks
Copy link
Contributor Author

Piinks commented Aug 19, 2022

@xu-baolin
Copy link
Member

@Piinks Hi, I just read the design document and downloaded your code today.
Next Monday I will look at the code implementation detail.
Have a nice weekend: )

@Piinks
Copy link
Contributor Author

Piinks commented Aug 19, 2022

Thank you very much! I really appreciate your expertise on this. :)

@@ -595,12 +601,18 @@ abstract class RenderSliverFloatingPersistentHeader extends RenderSliverPersiste
final double maxExtent = this.maxExtent;
final double paintExtent = maxExtent - _effectiveScrollOffset!;
final double layoutExtent = maxExtent - constraints.scrollOffset;
final double clampedPaintExtent = paintExtent.clamp(0.0, constraints.remainingPaintExtent);
final EdgeInsets contentInsets = _getInsetsForAxisDirection(
clampedPaintExtent + stretchOffset,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1, On mobile platform, the AppBar consume the top padding(system status bar) and paint it. This cause the Scrollbar mismatch at the initial state.
2, I also saw that the contentInsets was not updated synchronized when user scrolling. This cause the scrollbar shake and jump. This needs further investigation.

In my opinion, this is a meaningful new feature and would love to work on this feature with you if you don't mind.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1, On mobile platform, the AppBar consume the top padding(system status bar) and paint it. This cause the Scrollbar mismatch at the initial state.

Oh really? I thought since the app bar paints into that status bar space, the paintExtent would properly represent it in the reported insets.

2, I also saw that the contentInsets was not updated synchronized when user scrolling. This cause the scrollbar shake and jump. This needs further investigation.

Yeah exactly, I have been chasing this for a while trying to figure it out. I thought maybe we need to update the scrollbar painter more often, or maybe not listen to scroll metrics notifications while scroll notification are happening... I have not quote cracked it yet.

In my opinion, this is a meaningful new feature and would love to work on this feature with you if you don't mind.

I would love that too! I always enjoy collaborating with you. Thank you!

@@ -595,12 +601,18 @@ abstract class RenderSliverFloatingPersistentHeader extends RenderSliverPersiste
final double maxExtent = this.maxExtent;
final double paintExtent = maxExtent - _effectiveScrollOffset!;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm currently investigating the cause of the demo's bar shaking.
1,Here, we calculate the insets depended on the scroll offset, but updateGeometry only be called when layout occurs.
If the user scrolling without perform layout(you know this always happens), the content inset will not updated. Once the layout occurs after more scrolling, the content insets will updated and cause the bar shaking.

It seems that we also should update the geometry when scroll position changed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Demo (Tested on desktop to avoid status bar issues.)

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
          body: RawScrollbar(
            thumbVisibility: true,
            thickness: 20,
            thumbColor: Colors.red,
            child: ScrollConfiguration(
              behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
              child: CustomScrollView(
                primary: true,
                slivers: [
                  const SliverAppBar(
                    floating: true,
                    toolbarHeight: 100,
                    // pinned: true,
                    // snap: true,
                    // stretch: true,
                  ),
                  SliverList(
                      delegate: SliverChildBuilderDelegate(
                            (context, index) => Text('Item $index'),
                        childCount: 200,
                      )
                  ),
                ],
              ),
            ),
          )
      ),
    );
  }
}

@Kavantix
Copy link
Contributor

Looking at these changes I cannot help but wonder if it even makes sense to have a SliverAppBar. Also while attempting to implement a SliverPinnedFooter (see Kavantix/sliver_tools#20) I sort of came to this realization.
Having these headers and footers be part of the normal slivers brings all kinds of complications with it like these insets for the scrollbar and the paint order in case of building a footer.
For instance, say I have a list of slivers with a SliverAppBar later on, when and how should the scrollbar use this inset?

So what I’m trying to say is did we ever consider re-evaluating if SliverAppBar needs to be a sliver at all. Wouldn’t all of this be a lot easier if it was a box widget that you would wrap around the scrollview, it then could still use the scrollcontroller/position to determine things like expand and stretch and position and size the scrollview accordingly. This would automatically mean that the scrollbar and such will be rendered only on the actual scrollable part without having to add anything to the sliver geometry and having to think about all possible cases this could be used in.

@Piinks
Copy link
Contributor Author

Piinks commented Sep 19, 2022

Hey @Kavantix thank you for sharing your insights. I have wondered the same recently, as far as taking SliverAppBar's functionality out of the sliver and into a box. A couple of months ago I learned about the ScrollNotificationObserver that was added to AppBar in order to perform the scrolledUnder behavior from material design. I think it might be possible then to have the AppBar stretch, and float etc. by listening to scrolling events, I have not tried it yet though.

I have heard from a number of folks that SliverAppBar is really why they end up using slivers, and sometimes undesirably just so they can get those effects. Definitely worth investigating. I have not been able to resolve some edge case issues with this proposal, so I may consider experimenting with AppBar next as a solution.

@Kavantix
Copy link
Contributor

Interesting using scroll notifications means that the scroll under effect is always a frame behind. I guess that’s acceptable for it, it would not be for the AppBar though.

I’ll see if I can come up with a proof of concept for this

@Kavantix
Copy link
Contributor

Managed to get a (hacky) PoC working in this gist

It has a collapsible AppBar that can be pinned and/or floating.

@Piinks
Copy link
Contributor Author

Piinks commented Sep 20, 2022

Wow! Thank you @Kavantix! That is really neat.

@Piinks
Copy link
Contributor Author

Piinks commented Oct 28, 2022

Finally circling back here. I am planning on moving forward with this without the scrollbar. I think this makes sense for edge scrolling widgets like the overscroll indicators and refresh indicators. They always come from the end of the scroll view on one side or the other. With a scrollbar, any edge pinning sliver like SliverPersistentHeader can be introduced anywhere in a custom scroll view, and trying to dynamically adapt to the content isn't very reliable or consistent - or pretty in some cases. I'll reopen this as a new PR, and include some guidance on scrollbars.

@Hackmodford
Copy link

Finally circling back here. I am planning on moving forward with this without the scrollbar. I think this makes sense for edge scrolling widgets like the overscroll indicators and refresh indicators. They always come from the end of the scroll view on one side or the other. With a scrollbar, any edge pinning sliver like SliverPersistentHeader can be introduced anywhere in a custom scroll view, and trying to dynamically adapt to the content isn't very reliable or consistent - or pretty in some cases. I'll reopen this as a new PR, and include some guidance on scrollbars.

What ended up being the guidance on scrollbars?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
f: material design flutter/packages/flutter/material repository. f: scrolling Viewports, list views, slivers, etc. framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow scrolling notifications to report content-based insets
4 participants