13

I am using SliverAppBar and SliverListView in my project.

I need BorderRadius to my SliverList that is coming bottom of my SliverAppBar.

Here is screenshot what I need :

enter image description here

And here is my code:

Scaffold(
    body: CustomScrollView(
      slivers: <Widget>[
        SliverAppBar(
            backgroundColor: Colors.transparent,
            brightness: Brightness.dark,
            actions: <Widget>[
              IconButton(icon: Icon(Icons.favorite), onPressed: () {}),
              IconButton(icon: Icon(Icons.share), onPressed: () {})
            ],
            floating: false,
            pinned: false,
            //title: Text("Flexible space title"),
            expandedHeight: getHeight(context) - MediaQuery.of(context).padding.top,
            flexibleSpace: Container(
              height: double.infinity,
              width: double.infinity,
              decoration: BoxDecoration(
                image: DecorationImage(
                  fit: BoxFit.cover,
                  image: AssetImage("assets/images/Rectangle-image.png")
                )
              ),
            ),
            bottom: _bottomWidget(context),
          ),
           SliverList(
            delegate: SliverChildListDelegate(listview),
          ),
      ],
    ),
  )

So, with this code the UI is coming like this...

enter image description here

can suggest any other approach that i can take to achieve this kind of design...

nvoigt
  • 75,013
  • 26
  • 93
  • 142
black-hacker
  • 223
  • 4
  • 12
  • 1
    Does NOT seem to be possible at the moment. I just posted an issue on GitHub: https://github.com/flutter/flutter/issues/62781 Have you been able to find any workaround in the meantime? – Tomas Baran Aug 03 '20 at 10:30
  • For work around I removed everything related to the slivers and added scroll controller for custom scrolling effect. Not smooth as slivers but works for now. – black-hacker Aug 04 '20 at 11:08

9 Answers9

9

I achieved this design using SliverToBoxAdapter my code like this.

enter image description here

final sliver = CustomScrollView(
  slivers: <Widget>[
    SliverAppBar(),
    SliverToBoxAdapter(
      child: Container(
        color: Color(0xff5c63f1),
        height: 20,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.end,
          children: <Widget>[
            Container(
              height: 20,
              decoration: BoxDecoration(
                color: Colors.white,
                  borderRadius: BorderRadius.only(
                    topLeft: const Radius.circular(20.0),
                    topRight: const Radius.circular(20.0),
                  ),
              ),
            ),
          ],
        ),
      ),
    ),
    SliverList(),
  ],
);

I used 2 containers inside SliverToBoxAdapter.

SliverToBoxAdapter is between the Sliver Appbar and the Sliver List.

  1. first I create a blue (should be Appbar color) container for the corner edge.
  2. then I create the same height white container with border-radius inside the blue container for list view.

Preview on dartpad

yathavan
  • 2,051
  • 2
  • 18
  • 25
  • More description about that 2 Container can help me better. – black-hacker May 13 '20 at 16:35
  • @black-hacker I add the exact code what I used, and I updated the explanation. – yathavan May 15 '20 at 17:48
  • 6
    That wont work in case you have an image... it is working with because your color matches the app color, but if you have an image,, the image will be separated from the sliver list,, you need to have the image false behind the sliver list. – Mo Bdair Jun 08 '20 at 17:57
7

Solution

At the time of writing, there is no widget that would support this functionality. The way to do it is with Stack widget and with your own SliveWidget

Before:

enter image description here Here is your default code:


 import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flexible space title',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 2,
      child: Scaffold(
        body: CustomScrollView(
          slivers: <Widget>[
            SliverAppBar(
              backgroundColor: Colors.transparent,
              brightness: Brightness.dark,
              actions: <Widget>[IconButton(icon: Icon(Icons.favorite), onPressed: () {}), IconButton(icon: Icon(Icons.share), onPressed: () {})],
              floating: false,
              pinned: false,
              expandedHeight: 250 - MediaQuery.of(context).padding.top,
              flexibleSpace: Container(
                height: 550,
                width: double.infinity,
                decoration: BoxDecoration(
                    image: DecorationImage(
                        fit: BoxFit.cover,
                        image: NetworkImage(
                            'https://images.unsplash.com/photo-1561752888-21eb3b67eb4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=967&q=80'))),
              ),
              //bottom: _bottomWidget(context),
            ),
            SliverList(
              delegate: SliverChildListDelegate(_listview(50)),
            ),
          ],
        ),
      ),
    );
  }
}

List _listview(int count) {
  List<Widget> listItems = List();

  listItems.add(Container(
    color: Colors.black,
    height: 50,
    child: TabBar(
      tabs: [FlutterLogo(), FlutterLogo()],
    ),
  ));

  for (int i = 0; i < count; i++) {
    listItems.add(new Padding(padding: new EdgeInsets.all(20.0), child: new Text('Item ${i.toString()}', style: new TextStyle(fontSize: 25.0))));
  }

  return listItems;
}

After

enter image description here

And here is your code done with Stack and SliveWidget widgets:

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flexible space title',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 2,
      child: Scaffold(
        body: Stack(
          children: [
            Container(
              height: 550,
              width: double.infinity,
              decoration: BoxDecoration(
                  image: DecorationImage(
                      fit: BoxFit.cover,
                      image: NetworkImage(
                          'https://images.unsplash.com/photo-1561752888-21eb3b67eb4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=967&q=80'))),
            ),
            CustomScrollView(
              anchor: 0.4,
              slivers: <Widget>[
                SliverWidget(
                  child: Container(
                    width: double.infinity,
                    height: 100,
                    decoration: BoxDecoration(
                        color: Colors.yellow, borderRadius: BorderRadius.only(topLeft: Radius.circular(30), topRight: Radius.circular(30))),
                    child: TabBar(
                      tabs: [FlutterLogo(), FlutterLogo()],
                    ),
                  ),
                ),
                SliverList(
                  delegate: SliverChildListDelegate(_listview(50)),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

List _listview(int count) {
  List<Widget> listItems = List();

  for (int i = 0; i < count; i++) {
    listItems.add(
      Container( //NOTE: workaround to prevent antialiasing according to: https://github.com/flutter/flutter/issues/25009
        decoration: BoxDecoration(
            color: Colors.white, //the color of the main container
            border: Border.all(
                //apply border to only that side where the line is appearing i.e. top | bottom | right | left.
                width: 2.0, //depends on the width of the unintended line
                color: Colors.white)),
        child: Container(
          padding: EdgeInsets.all(20),
          color: Colors.white,
          child: new Text(
            'Item ${i.toString()}',
            style: new TextStyle(fontSize: 25.0),
          ),
        ),
      ),
    );
  }

  return listItems;
}

class SliverWidget extends SingleChildRenderObjectWidget {
  SliverWidget({Widget child, Key key}) : super(child: child, key: key);
  @override
  RenderObject createRenderObject(BuildContext context) {
    // TODO: implement createRenderObject
    return RenderSliverWidget();
  }
}

class RenderSliverWidget extends RenderSliverToBoxAdapter {
  RenderSliverWidget({
    RenderBox child,
  }) : super(child: child);

  @override
  void performResize() {}

  @override
  void performLayout() {
    if (child == null) {
      geometry = SliverGeometry.zero;
      return;
    }
    final SliverConstraints constraints = this.constraints;
    child.layout(constraints.asBoxConstraints(/* crossAxisExtent: double.infinity */), parentUsesSize: true);
    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: 100,
      paintOrigin: constraints.scrollOffset,
      cacheExtent: cacheExtent,
      maxPaintExtent: childExtent,
      hitTestExtent: paintedChildSize,
    );
    setChildParentData(child, constraints, geometry);
  }
}

Tomas Baran
  • 1,570
  • 2
  • 20
  • 37
  • There is actually even a better answer since my code produces an error and therefore curbs down the frame rate. Check out the @Kherel's solution—it's perfect! You can also read more about the error and the better answer here: https://stackoverflow.com/questions/63265729/error-slivergeometry-has-a-paintoffset-that-exceeds-the-remainingpaintextent-fr/63346806#63346806 – Tomas Baran Aug 14 '20 at 14:00
5

Use Stack. It's the best and smooth way I found and used. Preview

import 'dart:math';
import 'package:agro_prep/views/structure/constant.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

class CropDetailsPage extends StatelessWidget {
  const CropDetailsPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          SliverAppBar(
            backgroundColor: Colors.white,

            actions: <Widget>[
              IconButton(icon: Icon(Icons.share), onPressed: () {})
            ],
            floating: false,
            pinned: false,
            //title: Text("Flexible space title"),
            expandedHeight: 281.h,
            flexibleSpace: Stack(
              children: [
                const Positioned.fill(
                  child: FadeInImage(
                    image: NetworkImage(tempImage),
                    placeholder: const NetworkImage(tempImage),
                    // imageErrorBuilder: (context, error, stackTrace) {
                    //   return Image.asset('assets/images/background.jpg',
                    //       fit: BoxFit.cover);
                    // },
                    fit: BoxFit.cover,
                  ),
                ),
                Positioned(
                  child: Container(
                    height: 33.h,
                    decoration: const BoxDecoration(
                      color: Colors.white,
                      borderRadius: BorderRadius.vertical(
                        top: Radius.circular(40),
                      ),
                    ),
                  ),
                  bottom: -7,
                  left: 0,
                  right: 0,
                )
              ],
            ),
          ),
          SliverList(
              delegate: SliverChildBuilderDelegate((context, index) {
            return ListTile(
              tileColor: whiteColor,
              title: Text(Random().nextInt(100).toString()),
            );
          }, childCount: 15))
        ],
      ),
    );
  }
}
ouflak
  • 2,458
  • 10
  • 44
  • 49
Kaushal Zod
  • 101
  • 1
  • 2
  • 1
    In my case, I've added the second positioned container within my SliverPersistentHeader at `bottom: 0`. This solution works like a charm. – Keith OYS Sep 19 '22 at 16:19
2

So the best way to achieve your result is to use "bottom" poperty inside SliverAppBar. This will add your rounded container to bottom of appbar / start of sliverlist

bottom: PreferredSize(
                preferredSize: const Size.fromHeight(24),
                child: Container(
                  width: double.infinity,
                  decoration: const BoxDecoration(
                    borderRadius: BorderRadius.vertical(
                      top: Radius.circular(12),
                    ),
                    color: Colors.white,
                  ),
                  child: Column(
                    children: [
                      Padding(
                        padding: const EdgeInsets.symmetric(vertical: 10),
                        child: Container(
                          width: 40,
                          height: 4,
                          decoration: BoxDecoration(
                            color: Colors.black,
                            borderRadius: BorderRadius.circular(2),
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
              ),
gadyantor
  • 21
  • 2
1

Worked for me!

     SliverAppBar(
            pinned: true,
            floating: false,
            centerTitle: true,
            title: TextWidget(detail.title,
              weight: FontWeight.bold
            ),
            expandedHeight: MediaQuery.of(context).size.height/2.5,
            flexibleSpace: FlexibleSpaceBar(
              centerTitle: true,
              collapseMode: CollapseMode.parallax,
              background: Stack(
                children: [
                 // Carousel images
                  Swiper(
                      itemWidth: MediaQuery.of(context).size.width,
                      itemHeight: MediaQuery.of(context).size.height /3.5,
                      itemCount: 2,
                      pagination:  SwiperPagination.dots,
                      loop: detail.banners.length > 1,
                      itemBuilder: (BuildContext context, int index) {
                        return Image.network(
                            'https://image.com?image=123.png',
                            fit: BoxFit.cover
                        );
                      }
                  ),
                  //Border radius 
                  Align(
                    alignment: Alignment.bottomCenter,
                    child: Container(
                      color: Colors.transparent,
                      height: 20,
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.end,
                        children: <Widget>[
                          Container(
                            height: 10,
                            decoration: BoxDecoration(
                              color: Colors.white,
                              borderRadius: BorderRadius.only(
                                topLeft: const Radius.circular(10),
                                topRight: const Radius.circular(10),
                              ),
                            ),
                          ),
                        ],
                      ),
                    ),
                  )
                ],
              ),
            ),
          )
srburton
  • 349
  • 3
  • 8
0

Try This, It's a Simple Solution

import 'package:flutter/material.dart';

class SliveR extends StatelessWidget {
  const SliveR({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          SizedBox(
            width: double.infinity,
            child: Image.network(
              'https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2700&q=80',
              fit: BoxFit.cover,
              height: MediaQuery.of(context).size.height * 0.35,
            ),
          ),
          Align(
            alignment: Alignment.topCenter,
            child: Container(
              decoration: const BoxDecoration(
                borderRadius: BorderRadius.all(Radius.circular(30)),
              ),
              child: ClipRRect(
                borderRadius: const BorderRadius.all(Radius.circular(30)),
                child: CustomScrollView(
                  anchor: 0.3,
                  slivers: [
                    SliverToBoxAdapter(
                      child: Container(
                        height: 900,
                        decoration: const BoxDecoration(
                          color: Colors.white,
                          borderRadius: BorderRadius.only(
                            topLeft: Radius.circular(40.0),
                            topRight: Radius.circular(40.0),
                          ),
                          boxShadow: [
                            BoxShadow(
                              color: Colors.grey,
                              offset: Offset(0.0, 1.0), //(x,y)
                              blurRadius: 16.0,
                            ),
                          ],
                        ),
                        child: const Center(
                          child: Text(
                            'Hello',
                            style: TextStyle(color: Colors.grey),
                          ),
                        ),
                      ),
                    )
                  ],
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}
Waseem Imtiaz
  • 13
  • 1
  • 4
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Dec 22 '21 at 08:18
0
FlexibleSpaceBar(
              title: CustomText(
                  text: "Renaissance Concourse Hotel",
                  textSize: kSubtitle3FontSize,
                  fontWeight: kBold),
              centerTitle: true,
              collapseMode: CollapseMode.pin,
              background: Stack(
                children: [
                  CachedNetworkImage(
                    imageUrl:
                        "url",
                    width: DeviceUtils.getWidth(context),
                    fit: BoxFit.cover,
                    placeholder: (context, url) => const Center(
                      child: CircularProgressIndicator(),
                    ),
                    errorWidget: (context, url, error) =>
                        const Icon(Icons.error_rounded),
                  ),
                  Positioned(
                    bottom: 50,
                    right: 0,
                    left: 0,
                    child: ContainerPlus(
                      color: kWhiteColor,
                      child: const SizedBox(
                        height: 20,
                      ),
                      radius: RadiusPlus.only(
                        topLeft: kBorderRadiusValue10,
                        topRight: kBorderRadiusValue10,
                      ),
                    ),
                  )
                ],
              ))

enter image description here

Ufuk Zimmerman
  • 510
  • 6
  • 18
0

Apparently they will add a "DecoratedSliver" so that you can use decorations in slivers, I imagine this will attend the question posted here. The conversation went a long way (to the point it was accepted and reverted in Flutter 3.7 - 3.7 Release Notes, search "SliverDecoration") and is yet to be published I assume in the next Flutter update.

As of now, Flutter 3.10 was released in May 10th 2023 while the commit in question was approved on June 20th 2023. Check the change history here: https://github.com/flutter/flutter/pull/127823

-1

The idea is good but it looks odd in some cases.

You could give a borderRadius to your first element in your list

Container(
  decoration: BoxDecoration(
    borderRadius: BorderRadius.only(
      topRight: Radius.circular(index == 0 ? 15 : 0),
      topLeft: Radius.circular(index == 0 ? 15 : 0),
    ),
  ),
)

Hope this helps someone

Jerin
  • 688
  • 1
  • 9
  • 21