Search code examples
flutterdartsliver-grid

Add a border radius to SliverGrid background in flutter


I'm building an app with flutter and get stuck while working on building a grid of items using SliverGrid inside CustomScrollView and I'm not able to add border-radius to its background. Note, I can add a radius to the individual grid item.

This is what I tried

Scaffold(
  backgroundColor: Colors.orange,
  body: CustomScrollView(
    slivers: <Widget>[
      SliverAppBar(
        pinned: true,
        floating: true,
        expandedHeight: 250.0,
        flexibleSpace: FlexibleSpaceBar(
            background: Image.asset(
              'assets/images/000.png',
              fit: BoxFit.cover,
            ),
            title: Text('Hello there')),
      ),
      SliverGrid(
        gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
          maxCrossAxisExtent: 200.0,
          mainAxisSpacing: 10.0,
          crossAxisSpacing: 10.0,
          childAspectRatio: 4.0,
        ),
        delegate: SliverChildBuilderDelegate(
          (BuildContext context, int index) {
            return Container(
              margin: EdgeInsets.symmetric(
                horizontal: 15,
                vertical: 15,
              ),
              alignment: Alignment.center,
              color: Colors.teal[100 * (index % 9)],
              child: Text('grid item $index'),
            );
          },
          childCount: 30,
        ),
      ),
    ],
  ),
);

This picture below is what I got with the above code. And what I need now is to add a circular border-radius to topLeft and topRight of the orange part.

enter image description here


Solution

  • you could add a shape to the sliverAppBar

    return Scaffold(
          backgroundColor: Colors.orange,
          body: CustomScrollView(
            slivers: <Widget>[
              SliverAppBar(
                pinned: true,
                floating: true,
                expandedHeight: 250.0,
                shape: RoundedRectangleBorder(
                    borderRadius:
                       BorderRadius.vertical(bottom: Radius.circular(16))), //like this
                flexibleSpace: FlexibleSpaceBar(
                    background: Container(color: Colors.blueAccent), //changed it because I dont have the image asset
                    title: Text('Hello there')),
              ),
              SliverGrid(
                gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
                  maxCrossAxisExtent: 200.0,
                  mainAxisSpacing: 10.0,
                  crossAxisSpacing: 10.0,
                  childAspectRatio: 4.0,
                ),
                delegate: SliverChildBuilderDelegate(
                  (BuildContext context, int index) {
                    return Container(
                      margin: EdgeInsets.symmetric(
                        horizontal: 15,
                        vertical: 15,
                      ),
                      alignment: Alignment.center,
                      color: Colors.teal[100 * (index % 9)],
                      child: Text('grid item $index'),
                    );
                  },
                  childCount: 100,
                ),
              ),
            ],
          ),
        );
      }
    

    enter image description here

    If you want it the other way around maybe you should create a custom ShapeBorder and override the getOuterPath to get outside of the shape and make it look like the orange side is the one with the shape. Let me know if you wanted it that way to try and update the answer

    UPDATE

    I believe you are looking for something like this enter image description here

        class MyWidget extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: Colors.orange,
          body: CustomScrollView(
            slivers: <Widget>[
              SliverAppBar(
                pinned: true,
                floating: true,
                expandedHeight: 250.0,
                shape: _CustomShape(), //like this
                flexibleSpace: FlexibleSpaceBar(
                  background: Image.network(
                    "https://images.pexels.com/photos/396547/pexels-photo-396547.jpeg?auto=compress&cs=tinysrgb&h=350",
                    fit: BoxFit.cover,
                  ),
                  title: Text('Hello there'),
                ),
    
              ),
              SliverGrid(
                gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
                  maxCrossAxisExtent: 200.0,
                  mainAxisSpacing: 10.0,
                  crossAxisSpacing: 10.0,
                  childAspectRatio: 4.0,
                ),
                delegate: SliverChildBuilderDelegate(
                  (BuildContext context, int index) {
                    return Container(
                      margin: EdgeInsets.symmetric(
                        horizontal: 15,
                        vertical: 15,
                      ),
                      alignment: Alignment.center,
                      color: Colors.teal[100 * (index % 9)],
                      child: Text('grid item $index'),
                    );
                  },
                  childCount: 100,
                ),
              ),
            ],
          ),
        );
      }
    }
    
    class _CustomShape extends ShapeBorder {
      const _CustomShape({
        this.side = BorderSide.none,
        this.borderRadius = BorderRadius.zero,
      }) : assert(side != null),
           assert(borderRadius != null);
    
      final BorderRadiusGeometry borderRadius;
    
      /// The style of this border.
      final BorderSide side;
    
       @override
      EdgeInsetsGeometry get dimensions => EdgeInsets.all(side.width);
    
      @override
      ShapeBorder scale(double t) {
        return _CustomShape(
          side: side.scale(t),
          borderRadius: borderRadius * t,
        );
      }
    
      @override
      ShapeBorder lerpFrom(ShapeBorder a, double t) {
        assert(t != null);
        if (a is ContinuousRectangleBorder) {
          return ContinuousRectangleBorder(
            side: BorderSide.lerp(a.side, side, t),
            borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t),
          );
        }
        return super.lerpFrom(a, t);
      }
    
      @override
      ShapeBorder lerpTo(ShapeBorder b, double t) {
        assert(t != null);
        if (b is ContinuousRectangleBorder) {
          return ContinuousRectangleBorder(
            side: BorderSide.lerp(side, b.side, t),
            borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t),
          );
        }
        return super.lerpTo(b, t);
      }
    
      @override
      Path getInnerPath(Rect rect, { TextDirection textDirection }) {
        double length = 16;
        return Path()
          ..lineTo(0, rect.height - length)
          ..lineTo(rect.width, rect.height - length)
          ..lineTo(rect.width, 0)
          ..close();
      }
    
      @override
      Path getOuterPath(rect, {TextDirection textDirection}) {
        double length = 16; //its just a random number I came up with to test the border
        return Path()
          ..lineTo(0, rect.height)
          ..quadraticBezierTo(length / 4, rect.height - length, length, rect.height - length)
          ..lineTo(rect.width - length, rect.height - length)
          ..quadraticBezierTo(rect.width - (length / 4), rect.height - length, rect.width, rect.height)
          ..lineTo(rect.width, 0)
          ..close();
      }
    
      @override
      void paint(Canvas canvas, Rect rect, { TextDirection textDirection }) {
        if (rect.isEmpty)
          return;
        switch (side.style) {
          case BorderStyle.none:
            break;
          case BorderStyle.solid:
            final Path path = getOuterPath(rect, textDirection: textDirection);
            final Paint paint = side.toPaint();
            canvas.drawPath(path, paint);
            break;
        }
      }
    
      @override
      bool operator ==(Object other) {
        if (other.runtimeType != runtimeType)
          return false;
        return other is ContinuousRectangleBorder
            && other.side == side
            && other.borderRadius == borderRadius;
      }
    
      @override
      int get hashCode => hashValues(side, borderRadius);
    
    }
    

    What I did was create a custom ShapeBorder based on ContinuousRectangleBorder but changing the getOuterPath and getInnerPath to a constant value to make it look like this (this was for the example, if you want a custom class that can use in more than one situation maybe changing some other values in the constructor).

    I still used in the SliverAppBar because thats the widget that allows me to change the shape with the shape attribute, but with the getOuterPath I painted the curves going from the max height of the widget to maxHeight - 16 (just a random number I came up, its like the former example when I added BorderRadius.vertical(bottom: Radius.circular(16))). If you didn't have the Sliver AppBar and instead an AppBar in the Scaffold you could just wrap the CustomScrollView in a Card with no margin and a shape of RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16))) for a similar effect

    UPDATE WITH PACKAGE

    add flutter_group_sliver: ^0.0.2 to your pubspec.yaml dependencies

    dependencies:
      flutter:
        sdk: flutter
      flutter_group_sliver: ^0.0.2
    

    import it to your project and use the new class SliverGroupBuilder() inside CustomScrollView, it's basically a Container made into a Sliver so you can use the margin, decoration, padding option inside a Sliver

    import 'package:flutter_group_sliver/flutter_group_sliver.dart';
    Scaffold(
          backgroundColor: Colors.pink[100],
          body: CustomScrollView(
            slivers: <Widget>[
              SliverAppBar(
                pinned: true,
                floating: true,
                expandedHeight: 250.0,
                elevation: 0.0,
                forceElevated: false,
                flexibleSpace: FlexibleSpaceBar(
                  background: Image.network(
                    "https://happypixelmedia.com/wp-content/uploads/2019/10/Web-design-ideas-to-look-forward-to-in-2020-cover-1.jpg",
                    fit: BoxFit.cover,
                  ),
                  title: Text('Hello there'),
                ),
              ),
              SliverGroupBuilder(
                margin: EdgeInsets.zero,
                decoration: BoxDecoration(
                  color: Colors.orange,
                  borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
                ),
                child: SliverGrid(
                  gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
                    maxCrossAxisExtent: 200.0,
                    mainAxisSpacing: 10.0,
                    crossAxisSpacing: 10.0,
                    childAspectRatio: 4.0,
                  ),
                  delegate: SliverChildBuilderDelegate(
                        (BuildContext context, int index) {
                      return Container(
                        margin: EdgeInsets.symmetric(
                          horizontal: 15,
                          vertical: 15,
                        ),
                        alignment: Alignment.center,
                        color: Colors.teal[100 * (index % 9)],
                        child: Text('grid item $index'),
                      );
                    },
                    childCount: 100,
                  ),
                ),
              )
            ],
          ),
        )
    

    The Scaffold background is pink and the SliverGroupBuilder is orange with a BorderRadius.vertical(top: Radius.circular(16)),

    enter image description here

    Closed SliverAppBar

    enter image description here

    This approach gives you what you want, but you have to be careful with the color of the Scaffold backgound to make it different so you can see the border radius

    check https://pub.dev/packages/flutter_group_sliver#-readme-tab- for more info of the package