最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Flutter | Slivers 系列

    正文概述 掘金(345丶)   2021-08-14   748

    概述

    CustomScrollView:一个滚动的容器,改组件不接受任何 child,但是你可以直接提供 Slivers 已创建各种滚动效果,例如页面中有多个可滑动的列表,如 Appbar, 列表,网格,等这种就可以直接使用 SliverAppBar,SliverListSliverGrid

    Slivers 不是单独指一个组件,而是指的一个系列,所以以 Sliver 开头的组件都是这个系列的,但是他们都只能作用于 CustomScrollView 中。

    常用到的 Sliver 有,SliverAppbar,SliverList,SliverGrid,SliverToBoxAdapter 等

    简单的使用

    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))
            ],
          ),
        );
      }
    }
    

    运行效果如下:

    Flutter | Slivers 系列

    其实我们仔细一点就会发现,其实 ListView 和 GridView 等组件内部使用的都是 Slivers,

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

    那为什么要使用 Slivers 呢?最主要的原因就是可以在 slives 中添加多个组件,如在列表的上面和下面添加更多的内容。

    并且 slivers 中,如果存在多个列表的话也是支持动态加载的,而不是会一次性全部渲染完


    各式各样的 Slivers 组件

    SliverList

    在上面的例子中 SliverList 使用的是 SliverChildBuilderDelegate 这个delegate,它可以实现动态加载,当然 SliverList 中也有和 ListView 中一样的非动态加载的delegate,就是SliverChildListDelegate

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

    一般在列表数量较小并且显示内容确定的情况下可以使用次 delegate

    SliverFixedExtentList

    面的子元素中的宽高是动态的,需要手动设置高度,并且这种也不利于性能,所以我们可以使用 SliverFixedExtentList 来控制限制子元素的大小:

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

    未限制前:Flutter | Slivers 系列,限制后:Flutter | Slivers 系列

    SliverPrototypeExtentList

    一般情况下,只要固定了列表中元素的高度,就可以提升不小的性能,但是在实际的项目中,想要固定元素的高度是非常麻烦的,就算是列表中的元素只有一行文字,也有可能会出现问题,例如直接在系统层面修改字体的大小,这也会导致高度的固定导致渲染出来的效果不尽人意。但是有了 SliverPrototypeExtentList 就简单多了。

    在 SliverPrototypeExtentList 中,可以通过 prototypeItem 来传入一个原型,这个原型并不会渲染到屏幕上,在运行的过程中,Flutter 会将原型的尺寸计算出来,之后就会把所有的元素尺寸设置成这个原型的尺寸。

    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"),
                ],
              )),
        ],
      ),
    ),
    

    如上,子元素的大小都会和 prototypeItem 中元素的大小进行同步,我们和 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"),
                ],
              )),
        ],
      ),
    ),
    

    效果如下:

    使用 prototype:Flutter | Slivers 系列,使用 fixed:Flutter | Slivers 系列

    从图中可以看到,尽管高度固定到 40,但是由于 Text 的大小被修改了,所以渲染出来的还是有问题。


    SliverFillViewport

    它也接受一个 delegate,支持动态的加载,只不过内部的子元素会占满整个屏幕

    SliverFillViewport(
        delegate: SliverChildListDelegate([
      Container(color: Colors.red),
      Container(color: Colors.yellow),
      Container(color: Colors.blue),
    ]))
    
    Flutter | Slivers 系列

    SliverAppbar

    在 slivers 系列中,SliverAppbar 可以说是使用频率比较高的组件了,SliverAppbar 为应用栏提供了自定义滚动行为,下面我们来看一下

    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),
                    ],
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }
    

    上面是一个磨人的 SliverAppbar,并没有实现任何特殊效果,默认的效果如下:

    Flutter | Slivers 系列

    可以看到在滑动的过程中,SliverAppbar 被顶上去了,这也是非常正常的。接着我们来看一下都有哪些特殊效果吧

    特殊效果
    • floating

      SliverAppBar(
        title: Text("Sliver AppBar"),
        floating: true,
      )
      

      在向下滑动的时候,会首先将 SliveAppbar 显示出来,如下:

      Flutter | Slivers 系列
    • pinned :一直显示在顶部,无视滑动,这样就和普通的导航栏差不多了。区别就是在滑动的时候 SliveAppbar 的底部会有一点点影子

    • snap:在滑动停止之后,导航会自动全部显示出来,需要注意的是必须搭配 floating 一起使用,如下:

      SliverAppBar(
        title: Text("Sliver AppBar"),
        snap: true,
        floating: true,
      )
      
      Flutter | Slivers 系列
    • flexibleSpace:可展开拉伸的部分

      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,
          ],
        ),
      ),
      
    Flutter | Slivers 系列

    SliverOpacity

    透明组件,内部接受的是一个 sliver,所以需要用 SliverToAdapter 转一下

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

    SliverFillRemaining

    该组件会填满当前页面的剩余空间

    SliverFillRemaining(
      hasScrollBody: false,
      child: Center(
        child: CircularProgressIndicator(),
      ),
    )
    
    • hasScrollBody :当前组件中是否有可滚动的组件
    Flutter | Slivers 系列

    案例

    首先看一下实现的效果(由于是 gif 图,所以看起来有一点卡):

    Flutter | Slivers 系列
    • 准备数据

      接口来源于网络,仅供学习使用

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

      对应的数据类:

      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,
         //.....自行添加
        );
      
        @override
        String toString() {
          return "$id  ---  $name";
        }
      }
      
    • 首页

      class _DemoWidgetState extends State<DemoWidget> {
        List<Member> _member = [];
      
        @override
        Widget build(BuildContext context) {
          return Scaffold(
            appBar: AppBar(
              title: Text("案例"),
            ),
            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("预备生", Color(0xff91cdeb)), pinned: true),
                  _buildTeamList("预备生"),
                  SliverPersistentHeader(
                      delegate: _MyDelegate("荣誉毕业生", Color(0xff8ed2f5)),
                      pinned: true),
                  _buildTeamList("荣誉毕业生"),
                  SliverPersistentHeader(
                      delegate: _MyDelegate("S预备生", Color(0xff38b26d)), pinned: true),
                  _buildTeamList("S预备生"),
                  SliverPersistentHeader(
                      delegate: _MyDelegate("X", Color(0xffa7b0ba)), pinned: true),
                  _buildTeamList("X"),
                ],
              ),
            ),
          );
        }
      
        SliverGrid _buildTeamList(String teamName) {
          //进行筛选
          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: [
                    //动画
                    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,
          );
        }
      
        ///最高高度
        @override
        double get maxExtent => 35;
      
        ///最新高度
        @override
        double get minExtent => 35;
      
        ///重绘
        @override
        bool shouldRebuild(covariant _MyDelegate oldDelegate) {
          //如果 title 不相等,则重绘
          return oldDelegate.title != title;
        }
      }
      
      

      上面代码在 refresh 中进行了网络请求,然后进行解析数据,最后进行了刷新操作

      上面代码都很简单,不太熟悉的可能就是 SliverPersistentHeader 了,这是一个可以置顶的 header,它可以出现在视图的任何一个位置, pinnedfloating 属性用来控制收起是是否展示,具体意思和 SliverAppbar 中一样。

    • 详情页面

      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),
                        //长宽比
                        child: AspectRatio(
                          aspectRatio: 1,
                           // 和上面那个页面的动画对应,tag 必须一致
                          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("战队:", member.team),
                  _buildInfo("公司:", member.company),
                  _buildInfo("时间:", member.join_day),
                  _buildInfo("身高:", member.height),
                  _buildInfo("生日:", member.birth_day),
                  _buildInfo("星座:", member.star_sign_12),
                  _buildInfo("运势:", member.star_sign_48),
                  _buildInfo("爱好:", member.speciality),
                  _buildInfo("签名:", member.catch_phrase),
                ],
              ))
            ],
          ));
        }
      
        _buildInfo(String label, String content) {
          return Card(
            child: Padding(
              padding: EdgeInsets.symmetric(vertical: 25),
              child: Row(
                children: [Text(label), Text(content)],
              ),
            ),
          );
        }
      }
      

      上面代码中有一个问题,本来使用了 stretch 属性之后,在下拉的时候应该会有一个放大的效果,但是运行代码的时候并没有,有知道原因的同学可以讲一下



    起源地 » Flutter | Slivers 系列

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元