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
)
SliverList
在delegate
中传入SliverChildListDelegate
SliverList(
delegate: SliverChildListDelegate([
const FlutterLogo(),
const FlutterLogo(size: 100),
]),
)
SliverList
在delegate
中传入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;
}
}