An overview of the

CustomScrollView: A container for scrolling, which does not accept any children, but you can directly provide Slivers that have created various scrolling effects, such as pages with multiple sliding lists, such as appBars, lists, grids, You can use SliverAppBar,SliverList and SliverGrid directly

Slivers do not refer to a single component, but to a family of components, so components that start with Sliver are from that family, but they can only be used in CustomScrollView.

Common slivers include: SliverAppbar, SliverList, SliverGrid, SliverToBoxAdapter, etc

Since the child components of CustomScrollView can only be the Sliver family, if you want to put a normal component in it, you must use a SliverToBoxAdapter to fit it

Simple use

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      drawer: Drawer(),
      body: CustomScrollView(
        slivers: [
          SliverAppBar(
            title: Text("SliverAppbar"),
          ),
          SliverGrid(
            gridDelegate:
                SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4),
            delegate: SliverChildBuilderDelegate((context, index) {
              return Container(
                  color: Colors.primaries[index % Colors.primaries.length]);
            }, childCount: 40),
          ),
          SliverList(
              delegate: SliverChildBuilderDelegate((context, index) {
            return Container(
                height: 100,
                color: Colors.primaries[index % Colors.primaries.length]);
          }, childCount: 20))],),); }}Copy the code

The running effect is as follows:

In fact, if we look a little closer, we’ll see that the ListView and GridView components use Slivers internally,

ListView.builder({
 / /...
}) : assert(itemCount == null || itemCount >= 0),
     assert(semanticChildCount == null|| semanticChildCount <= itemCount!) , childrenDelegate = SliverChildBuilderDelegate( itemBuilder, childCount: itemCount, addAutomaticKeepAlives: addAutomaticKeepAlives, addRepaintBoundaries: addRepaintBoundaries, addSemanticIndexes: addSemanticIndexes, ),super(
 		//....
     );
Copy the code

So why use Slivers? The main reason is that you can add multiple components to SLives, such as adding more content above and below the list.

Slivers also support dynamic loading if there are multiple lists, rather than rendering them all at once


A variety of Slivers components

SliverList

Is used in the above example SliverList SliverChildBuilderDelegate this delegate, it can realize the dynamic loading, of course, also have and ListView in SliverList as the delegate of the dynamic loading, Is SliverChildListDelegate

SliverList(
    delegate: SliverChildListDelegate(
  [
    FlutterLogo(size: 100),
    FlutterLogo(size: 100),
    FlutterLogo(size: 100),]))Copy the code

You can use a sub-delegate when the list is small and the display content is determined.

SliverFixedExtentList

The width and height in the child elements of the face are dynamic and require manual setting of the height, which is also bad for performance, so we can use SliverFixedExtentList to limit the size of the child elements:

SliverFixedExtentList(
    itemExtent: 100,
    delegate: SliverChildListDelegate(
      [
        FlutterLogo(),
        FlutterLogo(),
        FlutterLogo(),
      ],
    ))
Copy the code

Before the restriction:After restriction:

SliverPrototypeExtentList

In general, fixing the height of an element in a list can improve performance, but in real projects, fixing the height of an element can be very troublesome. Even if the element in a list is only one line of text, there may be problems, such as changing the font size directly at the system level. This can also result in a fixed height that results in unsatisfactory renderings. But with SliverPrototypeExtentList is simpler.

In SliverPrototypeExtentList prototypeItem to pass in a prototype, the prototype does not apply colours to a drawing on the screen, in the process of running, Flutter will calculated by the size of the prototype, Then all the elements are set to the size of the prototype.

body: DefaultTextStyle(
  style: TextStyle(fontSize: 60, color: Colors.red),
  child: CustomScrollView(
    slivers: [
      SliverPrototypeExtentList(
          prototypeItem: Text(""),
          delegate: SliverChildListDelegate(
            [
              Text("Hello Word"),
              Text("Hello Word"),
              Text("Hello Word"() [[() [[() [()Copy the code

As shown above, the size of the child element is synchronized with the size of the prototypeItem element, so let’s compare this with SliverFixedExtentList

body: DefaultTextStyle(
  style: TextStyle(fontSize: 60, color: Colors.red),
  child: CustomScrollView(
    slivers: [
      SliverFixedExtentList(
          itemExtent: 40,
          delegate: SliverChildListDelegate(
            [
              Text("Hello Word"),
              Text("Hello Word"),
              Text("Hello Word"() [[() [[() [()Copy the code

The effect is as follows:

Using the prototype:, using fixed:

As you can see from the image, although the height is fixed at 40, the rendering is still problematic because the size of the Text has been changed.


SliverFillViewport

It also accepts a delegate, which supports dynamic loading, but the inner child elements fill the screen

SliverFillViewport(
    delegate: SliverChildListDelegate([
  Container(color: Colors.red),
  Container(color: Colors.yellow),
  Container(color: Colors.blue),
]))
Copy the code


SliverAppbar

One of the most frequently used components in the Slivers family is the SliverAppbar, which provides custom scrolling behavior for the app bar

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      drawer: Drawer(),
      body: DefaultTextStyle(
        style: TextStyle(fontSize: 60, color: Colors.red),
        child: CustomScrollView(
          slivers: [
            SliverAppBar(
              title: Text("Sliver AppBar"),
            ),
            SliverToBoxAdapter(child: Placeholder()),
            SliverList(
              delegate: SliverChildListDelegate(
                [
                  FlutterLogo(size: 200),
                  FlutterLogo(size: 200),
                  FlutterLogo(size: 200() [[() [[() [() [() [() [() }}Copy the code

Above is a grinding SliverAppbar that doesn’t implement any special effects. The default looks like this:

You can see that during the slide, the SliverAppbar is jacked up, which is also quite normal. Let’s take a look at some of the special effects

Special effects
  • floating

    SliverAppBar(
      title: Text("Sliver AppBar"),
      floating: true.)Copy the code

    When sliding down, the SliveAppbar is first displayed as follows:

  • Pinned: stay pinned to the top, ignore the swipe, so it is almost the same as the normal navigation bar. The difference is that when you slide the SliveAppbar has a little shadow at the bottom

  • 2. Snap: The navigation will all appear automatically when the slide stops. Note that it must be used together in the floating world, as follows:

    SliverAppBar(
      title: Text("Sliver AppBar"),
      snap: true,
      floating: true.)Copy the code

  • FlexibleSpace: part that can be extended

    SliverAppBar(
      // title: Text("Sliver AppBar"),
      expandedHeight: 300,
      stretch: true,
      flexibleSpace: FlexibleSpaceBar(
        background: FlutterLogo(),
        title: Text("FlexibleSpaceBar title"),
        collapseMode: CollapseMode.parallax,
        stretchModes: [
          StretchMode.blurBackground,
          StretchMode.zoomBackground,
          StretchMode.fadeTitle,
        ],
      ),
    ),
    Copy the code

SliverOpacity

The transparent component accepts a sliver internally, so it needs to be rotated with a SliverToAdapter

SliverOpacity(
  opacity: 0.5,
  sliver: SliverToBoxAdapter(
    child: FlutterLogo(
      size: 100,),),)Copy the code

SliverFillRemaining

This component fills up the remaining space of the current page

SliverFillRemaining(
  hasScrollBody: false,
  child: Center(
    child: CircularProgressIndicator(),
  ),
)
Copy the code
  • HasScrollBody: Whether there are scrollable components in the current component

case

Let’s take a look at the implementation first (it looks a bit stuck because it’s a GIF) :

  • To prepare data

    The interface is from the network and is only used for learning

    https://h5.48.cn/resource/jsonp/allmembers.php?gid=10
    Copy the code

    Corresponding data class:

    class Member {
      final String id;
      final String name;
      final String team;
      final String sid;
      final String gid;
      final String gname;
      final String sname;
      final String fname;
      final String tname;
      final String pid;
      final String pname;
      final String nickname;
      final String company;
      final String join_day;
      final String height;
      final String birth_day;
      final String star_sign_12;
      final String star_sign_48;
      final String speciality;
      final String hobby;
      final String experience;
      final String catch_phrase;
      final String status;
      final String ranking;
      final String tcolor;
      final String gcolor;
    
      String get avatarUrl => "https://www.snh48.com/images/member/zp_$id.jpg";
    
      Member(
        this.id,
        this.name,
       / /... To add
      );
    
      @override
      String toString() {
        return "$id  ---  $name"; }}Copy the code
  • Home page

    class _DemoWidgetState extends State<DemoWidget> {
      List<Member> _member = [];
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("Case"),
          ),
          body: RefreshIndicator(
            onRefresh: () async {
              setState(() => _member.clear());
              final url = "https://h5.48.cn/resource/jsonp/allmembers.php?gid=10";
              final res = await http.get(Uri.parse(url));
              if(res.statusCode ! =200) throw Error();
    
              final json = convert.jsonDecode(res.body);
              final members = (json["rows"] as List)
                  .map((e) => Member(
                        e['sid'], e["sname"],e["tname"], e["sid"], e["gid"],e["gname"],e["sname"],e["fname"],e["tname"],
                        e["pid"],e["pname"], e["nickname"], e["company"], e["join_day"], e["height"],    e["birth_day"],
                        e["star_sign_12"], e["star_sign_48"], e["speciality"], e["hobby"], e["experience"],
                        e["catch_phrase"],  e["status"], e["ranking"], e["tcolor"],e["gcolor"],
                      ))
                  .toList();
    
              setState(() => _member = members);
            },
            child: CustomScrollView(
              slivers: [
                SliverToBoxAdapter(),
                SliverPersistentHeader(
                    delegate: _MyDelegate("SII", Color(0xffae86bb)), pinned: true),
                _buildTeamList("SII"),
                SliverPersistentHeader(
                    delegate: _MyDelegate("NII", Color(0xff91cdeb)), pinned: true),
                _buildTeamList("NII"),
                SliverPersistentHeader(
                    delegate: _MyDelegate("HII", Color(0xffa7b0ba)), pinned: true),
                _buildTeamList("HII"),
                SliverPersistentHeader(
                    delegate: _MyDelegate("Prep student", Color(0xff91cdeb)), pinned: true),
                _buildTeamList("Prep student"),
                SliverPersistentHeader(
                    delegate: _MyDelegate("Honorary graduate", Color(0xff8ed2f5)),
                    pinned: true),
                _buildTeamList("Honorary graduate"),
                SliverPersistentHeader(
                    delegate: _MyDelegate("S Prep", Color(0xff38b26d)), pinned: true),
                _buildTeamList("S Prep"),
                SliverPersistentHeader(
                    delegate: _MyDelegate("X", Color(0xffa7b0ba)), pinned: true),
                _buildTeamList("X"[[(), [(), [(), [(); } SliverGrid _buildTeamList(String teamName) {
        // Filter
        final teamMember =
            _member.where((element) => element.team == teamName).toList();
        return SliverGrid(
          delegate: SliverChildBuilderDelegate((context, index) {
            Member m = teamMember[index];
            return InkWell(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  / / animation
                  Hero(
                      tag: m.avatarUrl,
                      child: ClipOval(
                        child: CircleAvatar(
                          child: Image.network(m.avatarUrl),
                          backgroundColor: Colors.white,
                        ),
                      )),
                  Text("${m.name}"),
                ],
              ),
              onTap: () => Navigator.of(context)
                  .push(MaterialPageRoute(builder: (_) => DetailPage(m))),
            );
          }, childCount: teamMember.length),
          gridDelegate:
              SliverGridDelegateWithMaxCrossAxisExtent(maxCrossAxisExtent: 120)); }}class _MyDelegate extends SliverPersistentHeaderDelegate {
      final String title;
      final Color color;
    
      _MyDelegate(this.title, this.color);
    
      @override
      Widget build(
          BuildContext context, double shrinkOffset, bool overlapsContent) {
        return Container(
          height: 35,
          child: FittedBox(child: Text(title, style: TextStyle())),
          color: color,
        );
      }
    
      ///The highest height
      @override
      double get maxExtent => 35;
    
      ///The latest highly
      @override
      double get minExtent => 35;
    
      ///redraw
      @override
      bool shouldRebuild(covariant _MyDelegate oldDelegate) {
        // If title is not equal, redraw
        return oldDelegate.title != title;
      }
    }
    
    Copy the code

    The code above makes the network request in REFRESH, parses the data, and finally refreshes

    The above code is simple, but what is not familiar is SliverPersistentHeader, which is a top pinned header that can appear anywhere in the view. The pinned and floating properties are used to control whether the fold is displayed or not. The exact meaning is the same as in SliverAppbar.

  • Details page

    class DetailPage extends StatelessWidget {
      final Member member;
    
      DetailPage(this.member);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            body: CustomScrollView(
          slivers: [
            SliverAppBar(
                expandedHeight: 300,
                pinned: true,
                stretch: true,
                flexibleSpace: FlexibleSpaceBar(
                  centerTitle: true,
                  title: Text("${member.name}"),
                  background: Center(
                    child: Padding(
                      padding: const EdgeInsets.all(100),
                      / / aspect ratio
                      child: AspectRatio(
                        aspectRatio: 1.// The tag must correspond to the animation on the page above
                        child: Hero(
                          tag: member.avatarUrl,
                          child: Material(
                            elevation: 4.0,
                            shape: CircleBorder(),
                            child: ClipOval(
                              child: Image.network(
                                member.avatarUrl,
                                fit: BoxFit.cover,
                              ),
                            ),
                          ),
                        ),
                      ),
                    ),
                  ),
                )),
            SliverList(
                delegate: SliverChildListDelegate(
              [
                _buildInfo("Clan:", member.team),
                _buildInfo("Company:", member.company),
                _buildInfo("Time:", member.join_day),
                _buildInfo("Height:", member.height),
                _buildInfo("Birthday:", member.birth_day),
                _buildInfo("Constellation:", member.star_sign_12),
                _buildInfo("Fortune:", member.star_sign_48),
                _buildInfo("Hobbies:", member.speciality),
                _buildInfo("Signature:", member.catch_phrase),
              ],
            ))
          ],
        ));
      }
    
      _buildInfo(String label, String content) {
        return Card(
          child: Padding(
            padding: EdgeInsets.symmetric(vertical: 25), child: Row( children: [Text(label), Text(content)], ), ), ); }}Copy the code

    There is a problem in the above code. After using the “Stretch” attribute, there should be an enlarged effect when pulling down the code, but it does not exist when running the code. If you know the reason, you can talk about it


Reference: B station Wang Shu is not bald

If this article is helpful to your place, we are honored, if there are mistakes and questions in the article, welcome to put forward!