Search code examples
flutterflutter-layoutflutter-sliver

Slivers equivalent for StatelessWidget


Which class to extend when creating slivers?

Say, I have a class FooSliver which returns a widget.

class FooSliver extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaybeSliverWidget(...);
  }
}

But I can't use this class directly in my slivers if the above class returns a non-sliver widget i.e. a RenderObject.

CustomScrollView(
  slivers: [
    FooSliver(),
  ],
)

So, which sliver class should I extend instead of StatelessWidget to make sure I always return a sliver widget from it?


Solution

  • You can create your custom sliver. You need to create a custom RenderSliverSingleBoxAdapter and SingleChildRenderObjectWidget

    class FooSliver extends SingleChildRenderObjectWidget {
      const FooSliver({super.key, super.child});
    
      @override
      RenderObject createRenderObject(BuildContext context) => FooRenderSliver();
    }
    
    class BarSliver extends SingleChildRenderObjectWidget {
      const BarSliver({super.key, super.child = const Text("Hello from Bar")});
    
      @override
      RenderObject createRenderObject(BuildContext context) => FooRenderSliver();
    }
    
    class FooRenderSliver extends RenderSliverSingleBoxAdapter {
      FooRenderSliver({super.child});
    
      @override
      void performLayout() {
        /// from  [RenderSliverToBoxAdapter]
        if (child == null) {
          geometry = SliverGeometry.zero;
          return;
        }
        final SliverConstraints constraints = this.constraints;
        child!.layout(constraints.asBoxConstraints(), parentUsesSize: true);
        final double childExtent;
        switch (constraints.axis) {
          case Axis.horizontal:
            childExtent = child!.size.width;
            break;
          case Axis.vertical:
            childExtent = child!.size.height;
            break;
        }
        assert(childExtent != null);
        final double paintedChildSize =
            calculatePaintOffset(constraints, from: 0.0, to: childExtent);
        final double cacheExtent =
            calculateCacheOffset(constraints, from: 0.0, to: childExtent);
    
        assert(paintedChildSize.isFinite);
        assert(paintedChildSize >= 0.0);
        geometry = SliverGeometry(
          scrollExtent: childExtent,
          paintExtent: paintedChildSize,
          cacheExtent: cacheExtent,
          maxPaintExtent: childExtent,
          hitTestExtent: paintedChildSize,
          hasVisualOverflow: childExtent > constraints.remainingPaintExtent ||
              constraints.scrollOffset > 0.0,
        );
        setChildParentData(child!, constraints, geometry!);
      }
    }
    

    And use like

    home: Scaffold(
      body: CustomScrollView(
        slivers: [
          FooSliver(
            child: Text("Hi From Foo sliver"),
          ),
          BarSliver()
        ],
      ),
    ),
    

    More about RenderSliver