How to mask-out the overlaped section, visible through the "translucent header sliver" in the NestedScrollView?

580

How about using CustomClipper for List itself? Because the list height is dynamic during scrolling, the clip height must be calculated dynamically. So I pass the clipHeight into the custom clipper.

To get the clipHeight, I use MediaQuery.of(context).size.height - header height. So I create another class to get this value.

      ...
      body: CustomWidget (
        child: ListView.builder(
        ...


class CustomWidget extends StatelessWidget {

 final Widget child;

 CustomWidget({this.child,Key key}):super(key:key);

  @override
  Widget build(BuildContext context) {
    return ClipRect(
      clipper: MyCustomClipper(clipHeight: MediaQuery.of(context).size.height-200),
      child: child,
    );
  }
}

class MyCustomClipper extends CustomClipper<Rect>{

  final double clipHeight;

  MyCustomClipper({this.clipHeight});

  @override
  getClip(Size size) {
    double top = math.max(size.height - clipHeight,0) ;
    Rect rect = Rect.fromLTRB(0.0, top, size.width, size.height);
    return rect;
  }

  @override
  bool shouldReclip(CustomClipper oldClipper) {
    return false;
  }
}
Share:
580
goodUser
Author by

goodUser

Updated on December 24, 2022

Comments

  • goodUser
    goodUser over 1 year

    The following code yields a scrollable list together with a "translucent pinned sliver header".

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            body: NestedScrollView(
              headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
                return [
                  SliverPersistentHeader(
                    delegate: _SliverPersistentHeaderDelegate(),
                    pinned: true,
                  ),
                ];
              },
              body: ListView.builder(
                itemBuilder: (context, index) {
                  return ListTile(
                    title: Container(
                      color: Colors.amber.withOpacity(0.3),
                      child: Text('Item $index'),
                    ),
                  );
                },
              ),
            ),
          ),
        );
      }
    }
    
    class _SliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
      @override
      Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
        return Container(
          color: Colors.blue.withOpacity(0.75),
          child: Placeholder(),
        );
      }
    
      @override double get maxExtent => 300;
      @override double get minExtent => 200;
      @override bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => true;
    }
    

    It's all good; except, I need the "header" to be transparent, but having it translucent, causes the underneathed list-items to get revealed (as per the screenshot below).

    So, how to "mask-out" the "list items" that are visible through the "translucent header"?

    The items are visible through the "pinned sliver header"

    • matehat
      matehat over 3 years
      If you want it to be "translucent", what do you expect to see through if not the list items?
    • goodUser
      goodUser over 3 years
      @matehat The "header" will contain some widgets; also, the whole thing will be "overlayed" on a background layer. However, the code is all good, except only I want the "list of items" to be "framed" in its own view (to not to be observable through the "header").
  • goodUser
    goodUser over 3 years
    The reason to use the pinned SliverPersistentHeader is that it's very performant; if only the "sliver list of items" could've been somehow "framed" in its "territory", then it would also be performant. Your code however, does provide a correct solution, but the approach rebuilds the Header widget together with the "container" widget for the list, on the scroll event; updating the currentHeight on every frame while scrolling, and they collectively cause some performance bottleneck. Could you please optimize your solution for a better performance. I appreciate your efforts on this.
  • Kherel
    Kherel over 3 years
    we trigger the setState method only in a small range of scroll offset, when we need to change the height of the header widget.
  • goodUser
    goodUser over 3 years
    That's exactly where the bottleneck happens (the "rebuild")! That's why I would like to somehow "frame" the "list of sliver items" in their "territory"!
  • goodUser
    goodUser over 3 years
    This is the best solution by far; intuitive and performant! Thank you very much.