一、初试Sliver

1.1 使用Sliver制作一个ListView

​ ListView的底层原理就是Sliver,我们可以用CustomScrollView先做出一个视窗(所谓视窗就是里面的内容比边框大),在其中使用SliverChildBuilderDelegate,而事实上,这也正是ListViewBuilder的底层做法。

CustomScrollView(		//定义一个视窗
  slivers: [
    SliverList(delegate: SliverChildBuilderDelegate((context, index) {
      return Container(
        height: 200, 
        color: Colors.primaries[index%Colors.primaries.length],
      );
    }))
  ],
)

截屏2021-11-02 下午10.19.31

1.2 更加灵活的ListView——加入任意Widget

​ 而使用Sliver,我们就可以实现更加灵活的功能,例如在顶部加入一个FlutterLogo:

CustomScrollView(
  slivers: [
    SliverToBoxAdapter(child: FlutterLogo(size:100),),	//加入一个FlutterLogo
    SliverList(
      delegate: SliverChildBuilderDelegate((context, index) {
        //使用SliverChildBuilderDelegate实现动态加载(Builder)
        //delegate: SliverChildListDelegate对应不用Builder的ListView
        return Container(
          height: 200, 
          color: Colors.primaries[index%Colors.primaries.length],
        );
    	}))
  ],
)

截屏2021-11-02 下午10.22.03

1.3 更加灵活的ListView——组合GridView

​ 我们也可以尝试组合GridView:

CustomScrollView(
  slivers: [
    /* 1. 用SliverToBoxAdapter包裹的普通Widget */
    const SliverToBoxAdapter(child: FlutterLogo(size:100),),
    /* 2. 用SliverGrid表示的GridView */
    SliverGrid(
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
      delegate: SliverChildBuilderDelegate((context, index) => Container(
        color: Colors.primaries[index % Colors.primaries.length]
      ), childCount: 6,  //也就是GridView中的itemCount
                                          )
    ),
    /* 3. 用SliverList表示的ListViewer.builder */
    SliverList(
      delegate: SliverChildBuilderDelegate((context, index) {
        return Container(
          height: 200, 
          color: Colors.primaries[index%Colors.primaries.length],
        );
      })),
  ],
)

截屏2021-11-02 下午11.00.19

二、其他的几个常用Sliver

2.1 使用itemExtent——SliverFixedExtentList

​ 您肯定还记得ListView中,为了方面滚动条的定位和跳转,我们会通过ListView中的itemExtent属性来固定每一项在主轴上的长度,例如我们需要定位到第100个元素,只需要将其乘上对应的数即可。

​ 这里的itemExtent属性不可以用列表内容Widget的widgetheight属性替代,因为那样不会获得滚动条的性能优化。

​ 在Sliver中我们需要使用SliverFixedExtentList来定义itemExtent属性,这样我们就可以让某些元素有固定的长度,另一些则不必。

2.2 自适应的itemExtent——SliverPrototypeExtentList

​ 通常我们很难直接得到内容Widget的高度,例如字号可能会因为老年模式而改变,因此我们可以使用SliverPrototypeExtentList来动态确定其itemExtent.

SliverPrototypeExtentList需要一个prototypeItem属性,它只是用来测量内容在主轴上的高度,并不会真的渲染到屏幕上。当字号调整时,我们只要配置了prototypeItem属性,它就会自动重新计算其itemExtent.

//自适应的itemExtent
SliverPrototypeExtentList(delegate: delegate, prototypeItem: prototypeItem),

2.3 使用PageView——SliverFillViewport

​ 回想一下PageView组件——其中的每一个项目都会沾满整个屏幕,通过滑动来切换,通常我们可以用它来做软件的开屏教程。

​ 在Sliver中,提供了一个叫做SliverFillViewport的组件,它也是PageView的底层原理。

2.4 自动回缩的华丽导航条——SliverAppBar

​ 我们可以使用SliverAppBar制作一个华丽的动态导航条,首先我们要去掉Scaffold中的AppBar属性,并用下面的代码添加到CustomScrollView中:

SliverAppBar(
  floating: true, //稍微一向下拉导航条就出现
  snap: true,
  //title: Text("111"),
  pinned: false,//不固定
  expandedHeight: 100,//导航条高度
  stretch: true,//使用更长的拉伸(flexibleSpace)
  flexibleSpace: FlexibleSpaceBar( 
    background: FlutterLogo(size:100),//导航条中的背景
    title: Text("111"),
    //上缩效果
    collapseMode: CollapseMode.parallax,
    //下拉效果
    stretchModes: [
      StretchMode.blurBackground,
      StretchMode.zoomBackground,
      StretchMode.fadeTitle
    ],
  ),
),

截屏2021-11-02 下午11.52.39

三、Sliver转换器

3.1 普通Widget的Sliver对应版本

例如:

  • Opacity => SliverOpacity

  • AnimatedOpacity =>SilverAnimatedOpacity

  • 值得注意的是,SizedBox直接对应空的SliverToBoxAdapter(),也就是不传任何东西

普通Widget基本都能找到其Sliver形式,可以直接用sliver属性嵌套(相当于普通Widget的child)属性。

3.2 特殊Sliver

  1. SliverFillRemaining——占据当前视窗的剩余全部空间,但最多占满一整页。
  2. 注意:使用SliverLayoutBuilder可以自己作出一个SliverFillRemaining组件哦~

完整实例:

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: const CustomScrollView(
        slivers: [
      SliverAppBar(
        floating: true, //稍微一向下拉导航条就出现
        snap: true,
        //title: Text("111"),
        pinned: false,//不固定
        expandedHeight: 100,//导航条高度
        stretch: true,//使用更长的拉伸(flexibleSpace)
        flexibleSpace: FlexibleSpaceBar( 
          background: FlutterLogo(size:100),//导航条中的背景
          title: Text("111"),
          //上缩效果
          collapseMode: CollapseMode.parallax,
          //下拉效果
          stretchModes: [
            StretchMode.blurBackground,
            StretchMode.zoomBackground,
            StretchMode.fadeTitle
          ],
        ),
      ),
      SliverToBoxAdapter(child: FlutterLogo(size: 100)),
      SliverToBoxAdapter(child: FlutterLogo(size: 100)),
      SliverToBoxAdapter(child: FlutterLogo(size: 100)),
      SliverFillRemaining(
        child:Center(	//用加载圈填满剩余视口
          child: CircularProgressIndicator(),
        )
      )
        ],
      ),
    );
  }
}

截屏2021-11-03 上午12.44.52

3.3 SliverAppBar原理——SliverPersistentHeader

​ 使用原理类似,

SliverToBoxAdapter(),
//防止SliverPersistentHeader成为最顶层的Sliver,以至于无法上拉刷新
SliverPersistentHeader(
	delegate:
  pinned: true,//像AppBar一样钉在最顶部
)