今天是Scroll篇最後一天
講講滾動綜藝大集合的神器
一樣先上圖
本節內容引用自Flutter實戰一書 6.1 可滚动组件简介 與 6.5 CustomScrollView
通常可滚动组件的子组件可能会非常多、占用的总高度也会非常大;如果要一次性将子组件全部构建出将会非常昂贵!
为此,Flutter中提出一个Sliver(中文为“薄片”的意思)概念,如果一个可滚动组件支持Sliver模型,那么该滚动可以将子组件分成好多个“薄片”(Sliver),只有当Sliver出现在视口中时才会去构建它,这种模型也称为“基于Sliver的延迟构建模型”。
可滚动组件中有很多都支持基于Sliver的延迟构建模型,如ListView、GridView,但是也有不支持该模型的,如SingleChildScrollView
。
也就是說ScrollView有分兩種:會
跟 不會
lazy的
會lazy的cell在Flutter裡的世界就叫做Sliver(不是Silver喔XDDD)
我們之前提到的children跟builder,只有builder裡的Widget才算Sliver
实际上Sliver版的可滚动组件和非Sliver版的可滚动组件
最大的区别
就是前者不包含滚动模型(自身不能再滚动),而后者包含滚动模型
也正因如此,CustomScrollView才可以将多个Sliver"粘"在一起,这些Sliver共用CustomScrollView的Scrollable。
SliverPadding、SliverAppBar等是和可滚动组件无关的,它们主要是为了结合CustomScrollView一起使用
NSCollectionLayoutGroup
在很多布局系统中都有ViewPort的概念,在Flutter中,术语ViewPort(视口),如无特别说明,则是指一个Widget的实际显示区域。例如,一个ListView的显示区域高度是800像素,虽然其列表项总高度可能远远超过800像素,但是其ViewPort仍然是800像素。
感覺應該就是Frame☘️☘️☘️
class LessonPageCustomScrollView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final space = 10.0;
final tintColor = Colors.pinkAccent.withOpacity(0.5);
final horizontalKey = new GlobalKey();
void scrollToTop() {
Scrollable.ensureVisible(horizontalKey.currentContext);
}
Widget _createCell(String title) {
return Card(
color: tintColor,
child: InkWell(
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(space),
child: Text(title, textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.w500
)
)
),
onTap: title.startsWith("horizontal") ? scrollToTop : null,
)
);
}
Widget buildNoArrowRefreshIndicator(
BuildContext context,
RefreshIndicatorMode refreshState,
double pulledExtent,
double refreshTriggerPullDistance,
double refreshIndicatorExtent,
) {
const Curve opacityCurve = Interval(0.4, 0.8, curve: Curves.easeInOut);
return Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: refreshState == RefreshIndicatorMode.drag
? Opacity(
opacity: opacityCurve.transform(
min(pulledExtent / refreshTriggerPullDistance, 1.0)
),
child: Icon(
CupertinoIcons.car_detailed,
color: CupertinoDynamicColor.resolve(CupertinoColors.inactiveGray, context),
size: 36.0,
),
)
: Opacity(
opacity: opacityCurve.transform(
min(pulledExtent / refreshIndicatorExtent, 1.0)
),
child: const CupertinoActivityIndicator(radius: 14.0),
),
),
);
}
return Material(
child: CustomScrollView(
slivers: <Widget>[
//頂
SliverAppBar(
backgroundColor: tintColor,
pinned: false,
expandedHeight: 200,
flexibleSpace: FlexibleSpaceBar(
title: Text("第十三堂課"),
background: Image.asset("resource/images/fantasy_unicorn.jpg",
fit: BoxFit.cover
),
),
),
//拉
CupertinoSliverRefreshControl(
builder: buildNoArrowRefreshIndicator,
onRefresh: () async {
print("拉爽的");
},
),
//上
SliverPadding(
padding: EdgeInsets.only(top: space, left: space, right: space),
sliver: SliverGrid(
delegate: SliverChildBuilderDelegate((ctx, idx){
return _createCell("Grid\n$idx");
}, childCount: 10),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: space,
crossAxisSpacing: space,
childAspectRatio: 1
)
),
),
//中
SliverFixedExtentList(
key: horizontalKey,
itemExtent: 100,
delegate: SliverChildBuilderDelegate ((ctx, idx) {
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: 10,
itemBuilder: (ctx, idx) {
return _createCell("horizontal\n$idx");
});
}, childCount: 1 )
),
//下
SliverList(
delegate: SliverChildBuilderDelegate ((ctx, idx) {
return _createCell("list $idx");
}, childCount: 10 )
)
],
),
);
}
}
下集預告:底部導航與頁籤