letsar / flutter_sticky_header

Flutter implementation of sticky headers for sliver
MIT License
903 stars 171 forks source link

Nested SliverStickyHeader #5

Closed bunopus closed 6 years ago

bunopus commented 6 years ago

Thank you for your library, it's super useful. I have one question: Is it possible to achieve something like this:

As you can see header with dates sticks to the screen as well as the side header with time (it is a https://github.com/google/iosched app). Example app have great section with side headers, but i tried to nest SliverStickyHeader and it doesn't work. Second Header doesn't stick to screen.

Code

// code from example app
class MainScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    List<Widget> slivers = new List<Widget>();

    // App Bar
    slivers.add(getAppBar());

    slivers.add( new SliverStickyHeader(
      // Tabs header
      header:_buildHeader(1, text: 'Tabs'),
      // Side header
      sliver: SliverStickyHeader(
       overlapsContent: true,
       header: _buildSideHeader(1),
       sliver: new SliverPadding(
       // other code is same, create grid

  }

  Widget getAppBar() {
    return SliverAppBar(
      expandedHeight: 60.0,
      floating: true,
      brightness: Brightness.light,
      title: Text('Main AppBar',
          style: TextStyle(color: Colors.black.withOpacity(1.0))),
      elevation: 0.0,
      centerTitle: true,
      backgroundColor: Color.fromRGBO(255, 255, 255, 1.0),
    );
  }
letsar commented 6 years ago

Hi @bunopus, thanks for the feedback 😃. All the slivers are in a CustomScrollView I presume? I am working on a way to have Nested SliverStickyHeader for another feature, but for now I am stucked 😞 . I will have another look to this thanks to your example. I will keep you posted.

letsar commented 6 years ago

I reproduced the issue, and saw that the SliverStickyHeader was painted under the AppBar. I think I will have to play with the SliverOverlapAbsorber to fix this.

sticky_header_pinned_under_app_bar

letsar commented 6 years ago

I found how to do it :grin:!

sticky_header_pinned_app_bar_ok

It's a little bit complex since you'll have to play with SliverOverlapAbsorber. If you find a simpler solution will you share it here please?

You can find the entire code for the view above here: https://gist.github.com/letsar/2e3cc98d328b3e84170abacf154e545f

bunopus commented 6 years ago

@letsar Wow! 😍 Thank you so much! Will try it right now!

letsar commented 6 years ago

You're welcome 😄 . If you find this library useful, don't hesitate to ⭐️ it 😉

bunopus commented 6 years ago

Tried your solution, and noticed one thing: app bar title ("Main AppBar") remains on the screen with tab bar. It's not exactly what i want. I think user don't need to see title all the time. In the original Google IO app (in first post) app bar title hides when user scrolls page. Maybe you know how to achieve that? Found PR https://github.com/flutter/flutter/pull/8345 and it says that you can pass floating: true, pinned: true but it doesn't work :-(

bunopus commented 6 years ago

Achieved this by adding another SliverAppBar :-) https://gist.github.com/bunopus/4a99dc9def2932e0aff2629f3905093a#file-tab_bar_view-dart-L36-L39 Thanks for your help!

letsar commented 6 years ago

Your're welcome and yes you're right it's not exactly what you wanted but it's more a SliverAppBarissue than a SliverStickyHeaderone :wink:.

workerbee22 commented 5 years ago

@bunopus You said you achieved this by adding another SliverAppBar but this means you have 2 SliverAppBars? Your gist link code gives me 2 x bars.

https://gist.github.com/bunopus/4a99dc9def2932e0aff2629f3905093a#file-tab_bar_view-dart-L36-L39

How did you make sure the second SliverAppBar ONLY has the TabBar in it?

bunopus commented 5 years ago

@workerbee22

How did you make sure the second SliverAppBar ONLY has the TabBar in it? Actually it turned out that it leads to bugs, so i leave it with Tab Bar on the screen, as Romain showed few messages above.

workerbee22 commented 5 years ago

Thanks @bunopus looks like that might be the only option at this stage. I can get it to work just like the Google I/O app where the AppBar bit of the SliverAppBar does in fact scroll off screen leaving just the TabBar pinned.

To see this use this code in this issue: https://github.com/flutter/flutter/issues/17518 , but with SliverAppBar having floating, pinned and snap all set to true.

!!! But as soon as I add flutter_sticky_header package inside a CustomScrollView as part of the TabBarView it stops working ie. AppBar bit of SliverAppBar no longer scrolls off screen.

So it seems like you can have either the floating app bar at the top OR sticky headers, but not both.

cgestes commented 2 years ago

This fork has commits related to nested SliverStickyHeader :)

https://github.com/UnderKoen/flutter_sticky_header

fennel-ptorchia commented 1 year ago

Is there an updated solution for this issue? This does not appear to work when using tabs and the nested scroll view, I'm looking to be able to pin headers that are inside of a custom scroll view in a tab bar view in the body of a nested scroll view.

PixelPatrik commented 1 year ago

When testing, my application crashed while changing tabs, because the handle did not remove listeners correctly. I needed to add the 'detach' function and remove the listener accordingly.

  @override
  void detach() {
    handle.removeListener(markNeedsLayout);
    super.detach();
  }

Here are the snippets if anyone is interested.

/// A sliver that has a sliver geometry based on the values stored in a
/// [SliverOverlapAbsorberHandle].
///
/// The [RenderSliverOverlapAbsorber] must be an earlier descendant of a common
/// ancestor [RenderViewport] (probably a [RenderNestedScrollViewViewport]), so
/// that it will always be laid out before the [RenderSliverObstructionInjector]
/// during a particular frame.
class RenderSliverObstructionInjector extends RenderSliverOverlapInjector {
  /// Creates a sliver that is as tall as the value of the given [handle]'s extent.
  ///
  /// The [handle] must not be null.
  RenderSliverObstructionInjector({
    required SliverOverlapAbsorberHandle handle,
    RenderSliver? child,
  })  : _handle = handle,
        super(handle: handle);

  double _currentLayoutExtent = 0;
  double _currentMaxExtent = 0;

  /// The object that specifies how wide to make the gap injected by this render
  /// object.
  ///
  /// This should be a handle owned by a [RenderSliverOverlapAbsorber] and a
  /// [RenderNestedScrollViewViewport].
  @override
  SliverOverlapAbsorberHandle get handle => _handle;
  SliverOverlapAbsorberHandle _handle;
  @override
  set handle(SliverOverlapAbsorberHandle value) {
    if (handle == value) return;
    if (attached) {
      handle.removeListener(markNeedsLayout);
    }
    _handle = value;
    if (attached) {
      handle.addListener(markNeedsLayout);
      if (handle.layoutExtent != _currentLayoutExtent ||
          handle.scrollExtent != _currentMaxExtent) markNeedsLayout();
    }
  }

  @override
  void attach(PipelineOwner owner) {
    super.attach(owner);
    handle.addListener(markNeedsLayout);
    if (handle.layoutExtent != _currentLayoutExtent ||
        handle.scrollExtent != _currentMaxExtent) {
      markNeedsLayout();
    }
  }

  @override
  void detach() {
    handle.removeListener(markNeedsLayout);
    super.detach();
  }

  @override
  void performLayout() {
    _currentLayoutExtent = handle.layoutExtent ?? 0;
    _currentMaxExtent = handle.layoutExtent ?? 0;
    geometry = SliverGeometry(
      scrollExtent: 0.0,
      paintExtent: _currentLayoutExtent,
      maxPaintExtent: _currentMaxExtent,
    );
  }
}
class SliverObstructionInjector extends SliverOverlapInjector {
  /// Creates a sliver that is as tall as the value of the given [handle]'s
  /// layout extent.
  ///
  /// The [handle] must not be null.
  ///
  const SliverObstructionInjector(
      {super.key, required SliverOverlapAbsorberHandle handle, super.sliver})
      : super(handle: handle);

  @override
  RenderSliverObstructionInjector createRenderObject(BuildContext context) {
    return RenderSliverObstructionInjector(
      handle: handle,
    );
  }
}