flutter-Sliver-flutter-sliver


title: flutter-Sliver
date: 2023-02-02 09:14:31.462
updated: 2023-02-06 10:52:49.171
url: https://hexo.start237.top/archives/flutter-sliver
categories:

  • IT技术
    tags:
  • flutter

走进Sliver的世界

什么是Sliver?

翻译过来是片、薄片的意思。在flutter中是和滚动有关系的。是可滚动部局中的一部分。
Sliver作为列表组件中的重要一部分,非常有必要了解其原理。
ViewProt是一个显示窗口,里面可以包含多个Sliver

各种各样的Sliver组件

CustomScrollView(
	slivers: []
)

类似于PageView组件,占满屏幕。这个组件是子组件占满屏幕

SliverFillViewport(
	delegate: SliverChildListDelegate(
         [const FlutterLogo(), const FlutterLogo()]
	)
),

SliverPrototypeExtentList 这个组件会通过属性prototypeItem来指定一个原型。然后在 delegate 中的子组件,会以这个原型来绘制屏幕。这个可以用在自定义调整列表的样式的功能。比如说一些老年人看不清楚屏幕的字体,可以使用这个功能来调节。

DefaultTextStyle(
            style: const TextStyle(color: Colors.red, fontSize: 30),
            child: SliverPrototypeExtentList(
                delegate: SliverChildListDelegate([const Text("1345556")]),
                prototypeItem: const Text("123")),
          )

SliverFixedExtentList 这个组件会和ListView中的 itemExtent一样。可限向下传递紧约束,限制列表的高度。

SliverFixedExtentList(
              delegate: SliverChildListDelegate(
                  [const FlutterLogo(), const FlutterLogo()]
               ),
              itemExtent: 200
)

SliverListdelegate中传入 SliverChildListDelegate

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

SliverListdelegate中传入 SliverChildBuilderDelegate类于于 ListView.build可以提升性能

SliverList(
            delegate: SliverChildBuilderDelegate((context, index) {
              return Container(
                  height: 60,
                  color: Colors.primaries[index % Colors.primaries.length]);
            }, childCount: 4),
          )

SliverGrid 可以渲染网格部局

SliverGrid(
              delegate: SliverChildBuilderDelegate((context, index) {
                return Container(
                    color: Colors.primaries[index % Colors.primaries.length]);
              }, childCount: 10),
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 3))

SliverAppBar组件

SliverAppBar的组件比较强大,可以做出现在流行的一些UI操作动作。下面是一些常用的属性。

const SliverAppBar({
    Key key,
    this.leading,//左侧的图标或文字,多为返回箭头
    this.automaticallyImplyLeading = true,//没有leading为true的时候,默认返回箭头,没有leading且为false,则显示title
    this.title,//标题
    this.actions,//标题右侧的操作
    this.flexibleSpace,//可以理解为SliverAppBar的背景内容区
    this.bottom,//SliverAppBar的底部区
    this.elevation,//阴影
    this.forceElevated = false,//是否显示阴影
    this.backgroundColor,//背景颜色
    this.brightness,//状态栏主题,默认Brightness.dark,可选参数light
    this.iconTheme,//SliverAppBar图标主题
    this.actionsIconTheme,//action图标主题
    this.textTheme,//文字主题
    this.primary = true,//是否显示在状态栏的下面,false就会占领状态栏的高度
    this.centerTitle,//标题是否居中显示
    this.titleSpacing = NavigationToolbar.kMiddleSpacing,//标题横向间距
    this.expandedHeight,//合并的高度,默认是状态栏的高度加AppBar的高度
    this.floating = false,//滑动时是否悬浮
    this.pinned = false,//标题栏是否固定
    this.snap = false,//配合floating使用
  })


在多认识一些Sliver组件

显示动画Sliver组件

SliverAnimatedOpacity(
          opacity: 0.5,
          duration: Duration(seconds: 1),
          sliver: SliverToBoxAdapter(
              child: FlutterLogo(
            size: 100,
          )),
        )
        

点击忽略

const SliverIgnorePointer(
          sliver: SliverAnimatedOpacity(
            opacity: 0.5,
            duration: Duration(seconds: 1),
            sliver: SliverToBoxAdapter(
                child: FlutterLogo(
              size: 100,
            )),
          ),
        ),

SliverPadding

const SliverPadding(
     padding: EdgeInsets.all(10),
     sliver: SliverToBoxAdapter(child: Text("123")),
)

SliverFillRemaining 这个组件有点牛,可以动态的计算在这一列中,剩下的视窗高度。类似于在Column中的Expand组件,扩展填剩余的空间。

SliverFillRemaining(
          child: Container(
            alignment: Alignment.center,
            child: const Text("123"),
          ),
        )

SliverGrid 中的 SliverGridDelegateWithMaxCrossAxisExtent可以限制交叉轴网格的宽度。

SliverGrid(
            delegate: SliverChildListDelegate([
              const Icon(Icons.access_time_rounded),
              const Icon(Icons.access_time_rounded),
              const Icon(Icons.access_time_rounded),
              const Icon(Icons.access_time_rounded),
              const Icon(Icons.access_time_rounded),
            ]),
            gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
                maxCrossAxisExtent: 180))

SliverGridDelegateWithFixedCrossAxisCount 可以限制网格交叉轴方向的个数。

gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 10)

SliverLayoutBuilder 类似于 LayoutBuilder组件


SliverLayoutBuilder(
          builder: ((p0, sliverConstraints) {
            print(sliverConstraints);
            return const SliverToBoxAdapter(
              child: Placeholder(
                fallbackHeight: 200,
              ),
            );
          }),
        )

从ListView 转到Sliver

一个小练习
请求的Apiurl https://h5.48.cn/resource/jsonp/allmembers.php?gid=10
注释的代码是ListView的
代码

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final int _counter = 0;
  List<Member> _members = [];

  
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: const Text('app bar')),
        floatingActionButton: FloatingActionButton(
          onPressed: () async {
            String url =
                'https://h5.48.cn/resource/jsonp/allmembers.php?gid=10';
            var res = await http.get(Uri.parse(url));
            if (res.statusCode != 200) {
              throw ('error');
            }

            final json = convert.jsonDecode(res.body);

            List<Member> members = json['rows'].map<Member>((row) {
              return Member(
                  id: row['sid'], name: row['sname'], tname: row['tname']);
            }).toList();
            setState(() {
              _members = members;
            });
          },
          child: const Icon(
            Icons.plus_one,
            color: Colors.white,
            size: 20,
          ),
        ),
        body: CustomScrollView(
          slivers: [
            SliverList(
              delegate: SliverChildBuilderDelegate(childCount: _members.length,
                  (context, index) {
                Member m = _members[index];
                return ListTile(
                  title: Text(m.name),
                  subtitle: Text(m.id),
                  leading: ClipOval(
                    child: CircleAvatar(
                        backgroundColor: Colors.transparent,
                        child: Image.network(m.avatarUrl)),
                  ),
                  trailing: Text(m.tname),
                );
              }),
            )
          ],
        ));
    // body: ListView.builder(
    //     itemCount: _members.length,
    //     itemBuilder: ((context, index) {
    //       Member m = _members[index];
    //       return ListTile(
    //         title: Text(m.name),
    //         subtitle: Text(m.id),
    //         leading: ClipOval(
    //           child: CircleAvatar(
    //               backgroundColor: Colors.transparent,
    //               child: Image.network(m.avatarUrl)),
    //         ),
    //       );
    //     })));
  }
}

class Member {
  final String id;
  final String name;
  final String tname;
  Member({required this.id, required this.name, required this.tname});
  
  String toString() {
    return "$id $name $tname";
  }

  get avatarUrl => "https://www.snh48.com/images/member/zp_$id.jpg";
}

SliverPersistentHeader

这个神奇的组件,可以做为分组的SliverListView的标题组件,像SliverAppBar一样的,可以吸咐在顶上。

class _MyHomePageState extends State<MyHomePage> {
  final int _counter = 0;
  List<Member> _members = [];
  inint() async {
    String url = 'https://h5.48.cn/resource/jsonp/allmembers.php?gid=10';
    var res = await http.get(Uri.parse(url));
    if (res.statusCode != 200) {
      throw ('error');
    }

    final json = convert.jsonDecode(res.body);

    List<Member> members = json['rows'].map<Member>((row) {
      return Member(id: row['sid'], name: row['sname'], tname: row['tname']);
    }).toList();
    setState(() {
      _members = members;
    });
  }

  SliverGrid _SliverList(String tname) {
    List<Member> tnameMembers =
        _members.where((element) => element.tname == tname).toList();

    return SliverGrid(
      gridDelegate:
          const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4),
      delegate: SliverChildBuilderDelegate(childCount: tnameMembers.length,
          (context, index) {
        Member m = tnameMembers[index];
        return Column(
          children: [
            ClipOval(
              child: CircleAvatar(
                  backgroundColor: Colors.transparent,
                  child: Image.network(m.avatarUrl)),
            ),
            Text(m.name)
          ],
        );
      }),
    );
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: const Text('app bar')),
        floatingActionButton: FloatingActionButton(
          onPressed: inint,
          child: const Icon(
            Icons.plus_one,
            color: Colors.white,
            size: 20,
          ),
        ),
        body: CustomScrollView(
          slivers: [
          // 我在这个地方
            SliverPersistentHeader(
                pinned: true,
                delegate:
                    _MyDelegate(title: 'SII', color: const Color(0xffae86bb))),
            _SliverList('SII'),
            SliverPersistentHeader(
                pinned: true,
                delegate:
                    _MyDelegate(title: 'SII', color: const Color(0xffae86bb))),
            _SliverList('SII')
          ],
        ));
  }
}

class Member {
  final String id;
  final String name;
  final String tname;
  Member({required this.id, required this.name, required this.tname});
  
  String toString() {
    return "$id $name $tname";
  }

  get avatarUrl => "https://www.snh48.com/images/member/zp_$id.jpg";
}

// 这个是SliverPersistentHeaderDelegate的delegate的值
class _MyDelegate extends SliverPersistentHeaderDelegate {
  final String title;
  final Color color;
  _MyDelegate({required this.title, required this.color});
  final double height = 40;
  
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    return Container(
      decoration: BoxDecoration(color: color),
      height: height,
      child: Text(title,
          textAlign: TextAlign.center, style: const TextStyle(height: 2.0)),
    );
  }

  
  double get maxExtent => height;

  
  double get minExtent => height;

  
  bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
    return true;
  }
}

设置一个有SliverBar的界面

直接上代码,比较干货的是设置SliverBar那的布局的部分。


import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert' as convert;

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.pink,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final int _counter = 0;
  List<Member> _members = [];

  inint() async {
    String url = 'https://h5.48.cn/resource/jsonp/allmembers.php?gid=10';
    var res = await http.get(Uri.parse(url));
    if (res.statusCode != 200) {
      throw ('error');
    }
    final json = convert.jsonDecode(res.body);
    List<Member> members = json['rows'].map<Member>((row) {
      return Member.fromJson(row);
    }).toList();
    setState(() {
      _members = members;
    });
  }

  SliverGrid _SliverList(String tname) {
    List<Member> tnameMembers =
        _members.where((element) => element.tname == tname).toList();

    return SliverGrid(
      gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
          maxCrossAxisExtent: 120),
      //const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4),
      delegate: SliverChildBuilderDelegate(childCount: tnameMembers.length,
          (context, index) {
        Member m = tnameMembers[index];
        return InkWell(
          onTap: () {
            Navigator.of(context).push(MaterialPageRoute(
                builder: (_) => DetailPage(
                      member: m,
                    )));
          },
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              Hero(
                tag: m.avatarUrl,
                child: ClipOval(
                  child: CircleAvatar(
                      backgroundColor: Colors.transparent,
                      child: Image.network(m.avatarUrl)),
                ),
              ),
              Text(m.sname as String)
            ],
          ),
        );
      }),
    );
  }

  
  void initState() {
    super.initState();
    inint();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: const Text('app bar')),
        body: CustomScrollView(
          slivers: [
            SliverPersistentHeader(
                pinned: true,
                delegate:
                    _MyDelegate(title: 'SII', color: const Color(0xffae86bb))),
            _SliverList('SII'),
            SliverPersistentHeader(
                pinned: true,
                delegate:
                    _MyDelegate(title: 'HII', color: const Color(0xffae86bb))),
            _SliverList('HII')
          ],
        ));
  }
}

class _MyDelegate extends SliverPersistentHeaderDelegate {
  final String title;
  final Color color;
  _MyDelegate({required this.title, required this.color});
  final double height = 40;
  
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    return Container(
      decoration: BoxDecoration(color: color),
      height: height,
      child: Text(title,
          textAlign: TextAlign.center, style: const TextStyle(height: 2.0)),
    );
  }

  
  double get maxExtent => height;

  
  double get minExtent => height;

  
  bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
    return true;
  }
}

class DetailPage extends StatefulWidget {
  final Member member;
  const DetailPage({Key? key, required this.member}) : super(key: key);
  
  State<StatefulWidget> createState() {
    return _DetailPage();
  }
}

class _DetailPage extends State<DetailPage> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
        // 这个地方是重点
          SliverAppBar(
            expandedHeight: 300,
            pinned: true,
            backgroundColor: Colors.pink[100],
            leading: const BackButton(color: Colors.white),
            flexibleSpace: FlexibleSpaceBar(
                background: Stack(
                  children: [
                    Column(
                      crossAxisAlignment: CrossAxisAlignment.stretch,
                      children: [
                        Expanded(
                          flex: 1,
                          child: Image.network(
                              'https://tse1-mm.cn.bing.net/th/id/OIP-C.nohkiwaREImEfbERYn_3iAHaE7?pid=ImgDet&rs=1',
                              fit: BoxFit.cover),
                        ),
                        Container(
                          height: 2,
                          color: Colors.pink,
                        ),
                        Expanded(
                          flex: 1,
                          child: Container(),
                        ),
                      ],
                    ),
                    Center(
                      child: Padding(
                        padding: const EdgeInsets.all(100.0),
                        child: Material(
                          elevation: 10,
                          shape: const CircleBorder(),
                          child: AspectRatio(
                            aspectRatio: 1,
                            child: ClipOval(
                                child: Hero(
                              tag: widget.member.avatarUrl,
                              child: Image.network(widget.member.avatarUrl,
                                  fit: BoxFit.cover),
                            )),
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
                title: Text(
                  widget.member.sname as String,
                  style: const TextStyle(color: Colors.white),
                )),
          ),
          SliverList(
            delegate: SliverChildListDelegate([
              ListTile(
                title: const Text("身高"),
                trailing: Text(widget.member.height as String),
              ),
              ListTile(
                title: const Text("身高"),
                trailing: Text(widget.member.height as String),
              ),
              ListTile(
                title: const Text("身高"),
                trailing: Text(widget.member.height as String),
              ),
              ListTile(
                title: const Text("身高"),
                trailing: Text(widget.member.height as String),
              ),
              ListTile(
                title: const Text("身高"),
                trailing: Text(widget.member.height as String),
              ),
              ListTile(
                title: const Text("身高"),
                trailing: Text(widget.member.height as String),
              ),
              ListTile(
                title: const Text("身高"),
                trailing: Text(widget.member.height as String),
              ),
              ListTile(
                title: const Text("身高"),
                trailing: Text(widget.member.height as String),
              ),
              ListTile(
                title: const Text("身高"),
                trailing: Text(widget.member.height as String),
              ),
              ListTile(
                title: const Text("身高"),
                trailing: Text(widget.member.height as String),
              ),
              ListTile(
                title: const Text("身高"),
                trailing: Text(widget.member.height as String),
              ),
              ListTile(
                title: const Text("身高"),
                trailing: Text(widget.member.height as String),
              ),
            ]),
          ),
        ],
      ),
    );
  }
}

class Member {
  String? sid;
  String? gid;
  String? gname;
  String? sname;
  String? fname;
  String? pinyin;
  String? abbr;
  String? tid;
  String? tname;
  String? pid;
  String? pname;
  String? nickname;
  String? company;
  String? joinDay;
  String? height;
  String? birthDay;
  String? starSign12;
  String? starSign48;
  String? birthPlace;
  String? speciality;
  String? hobby;
  String? experience;
  String? catchPhrase;
  String? weiboUid;
  String? weiboVerifier;
  String? bloodType;
  String? tiebaKw;
  String? status;
  String? ranking;
  String? pocketId;
  String? tcolor;
  String? gcolor;

  get avatarUrl => "https://www.snh48.com/images/member/zp_${sid ?? ''}.jpg";

  Member(
      {this.sid,
      this.gid,
      this.gname,
      this.sname,
      this.fname,
      this.pinyin,
      this.abbr,
      this.tid,
      this.tname,
      this.pid,
      this.pname,
      this.nickname,
      this.company,
      this.joinDay,
      this.height,
      this.birthDay,
      this.starSign12,
      this.starSign48,
      this.birthPlace,
      this.speciality,
      this.hobby,
      this.experience,
      this.catchPhrase,
      this.weiboUid,
      this.weiboVerifier,
      this.bloodType,
      this.tiebaKw,
      this.status,
      this.ranking,
      this.pocketId,
      this.tcolor,
      this.gcolor});

  Member.fromJson(Map<String, dynamic> json) {
    sid = json['sid'];
    gid = json['gid'];
    gname = json['gname'];
    sname = json['sname'];
    fname = json['fname'];
    pinyin = json['pinyin'];
    abbr = json['abbr'];
    tid = json['tid'];
    tname = json['tname'];
    pid = json['pid'];
    pname = json['pname'];
    nickname = json['nickname'];
    company = json['company'];
    joinDay = json['join_day'];
    height = json['height'];
    birthDay = json['birth_day'];
    starSign12 = json['star_sign_12'];
    starSign48 = json['star_sign_48'];
    birthPlace = json['birth_place'];
    speciality = json['speciality'];
    hobby = json['hobby'];
    experience = json['experience'];
    catchPhrase = json['catch_phrase'];
    weiboUid = json['weibo_uid'];
    weiboVerifier = json['weibo_verifier'];
    bloodType = json['blood_type'];
    tiebaKw = json['tieba_kw'];
    status = json['status'];
    ranking = json['ranking'];
    pocketId = json['pocket_id'];
    tcolor = json['tcolor'];
    gcolor = json['gcolor'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = <String, dynamic>{};
    data['sid'] = sid;
    data['gid'] = gid;
    data['gname'] = gname;
    data['sname'] = sname;
    data['fname'] = fname;
    data['pinyin'] = pinyin;
    data['abbr'] = abbr;
    data['tid'] = tid;
    data['tname'] = tname;
    data['pid'] = pid;
    data['pname'] = pname;
    data['nickname'] = nickname;
    data['company'] = company;
    data['join_day'] = joinDay;
    data['height'] = height;
    data['birth_day'] = birthDay;
    data['star_sign_12'] = starSign12;
    data['star_sign_48'] = starSign48;
    data['birth_place'] = birthPlace;
    data['speciality'] = speciality;
    data['hobby'] = hobby;
    data['experience'] = experience;
    data['catch_phrase'] = catchPhrase;
    data['weibo_uid'] = weiboUid;
    data['weibo_verifier'] = weiboVerifier;
    data['blood_type'] = bloodType;
    data['tieba_kw'] = tiebaKw;
    data['status'] = status;
    data['ranking'] = ranking;
    data['pocket_id'] = pocketId;
    data['tcolor'] = tcolor;
    data['gcolor'] = gcolor;
    return data;
  }
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值