CustomScrollView
來源: CustomScrollView 是可以使用
Sliver
來自定義滾動模型(效果)的組件。舉個例子,假設有一個頁面,頂部需要一個GridView
,底部需要一個ListView
,而要求整個頁面的滑動效果是統一的,即它們看起來是一個整體。如果使用GridView+ListView
來實現的話,就不能保證一致的滑動效果,而CustomScrollView
就可以實現這個效果。
實現源碼請查看原文:CustomScrollView
簡易版代碼如下:
class CustomScrollViewTestRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Material(
child: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
//AppBar,包含一個導航欄
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
sliver: new SliverGrid(
//Grid 內容
),
),
new SliverFixedExtentList(
// ListView 內容
),
],
),
);
}
}
添加頭部
可以使用
SliverPersistentHeader
來實現,也可以直接在ListView
頂部添加一個Head
來實現。
效果如下:
源碼實現也很簡單,這裏直接在CustomScrollView
裏面嵌套兩個SliverFixedExtentList
,一個顯示Head 頭部
,一個顯示List 列表
。
參考:Flutter:Slivers大家族,讓滑動視圖的組合變得很簡單!
使用 SliverPersistentHeader
來實現需要自定義類
繼承SliverPersistentHeaderDelegate
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate{
_SliverAppBarDelegate({
@required this.minHeight,
@required this.maxHeight,
@required this.child,
});
final double minHeight;
final double maxHeight;
final Widget child;
@override
double get minExtent => minHeight;
@override
double get maxExtent => math.max(maxHeight, minHeight);
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return new SizedBox.expand(child: child);
}
@override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return maxHeight != oldDelegate.maxHeight ||
minHeight != oldDelegate.minHeight ||
child != oldDelegate.child;
}
}
使用的時候直接添加到sliver
裏面即可
SliverPersistentHeader(
pinned: false,
floating: false,
delegate: _SliverAppBarDelegate(
minHeight: 60.0,
maxHeight: 250.0,
child: Container(
color: Colors.blue,
child: Center(
child: Text('header',style: TextStyle(color: Colors.white),),
)
),
),
),
視差滑動
視差滾動是指讓多層背景以不同的速度移動,在形成立體滾動效果的同時,還能保證良好的視覺體驗。
效果圖如下:
要實現該效果主要用到了SliverAppBar
組件。
做過Android
開發的都知道CollapsingToolbarLayout
控件,它可以實現頁面頭部展開、合併的視差效果。在Flutter
中是通過SliverAppBar
組件實現類似的效果。
直接查看SliverAppBar
組件支持的字段吧:
const SliverAppBar({
Key key,
this.leading,// 左側的widget
this.automaticallyImplyLeading = true,
this.title,//標題
this.actions,//標題右側的操作
this.flexibleSpace,// 背景widget,如 FlexibleSpaceBar 可設置標題,背景圖片,標題邊距等
this.bottom, // 底部區
this.elevation,//陰影
this.forceElevated = false,//是否顯示陰影
this.backgroundColor,//背景顏色
this.brightness,//狀態欄主題
this.iconTheme,// 圖標主題
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,// 滑動時標題欄跟隨移動並固定在頂部, pinned 和 floating 效果的組合
this.stretch = false,// 標題跟隨滑動時拉伸,收縮
this.stretchTriggerOffset = 100.0,// 標題跟隨滑動時拉伸,收縮的偏移量
this.onStretchTrigger,// 跟隨滑動時拉伸,收縮的回調
this.shape,// 陰影形狀,elevation 大於0 纔會顯示
})
在字段的後面都寫明瞭相應的介紹,只需要在使用的時候設置相關的參數即可實現效果。
監聽滑動
ScrollController
使用列表提供的controller
字段,並調用監聽方法監聽滑動距離
_controller.addListener((){
print('_controller offset : ${_controller.offset}');
});
NotificationListener
- 使用
ScrollNotification
控件去監聽滾動列表。
@override
Widget build(BuildContext context) {
return Material(
child: NotificationListener<ScrollNotification>(
onNotification: (scrollNotification) {
//註冊通知回調
if (scrollNotification is ScrollStartNotification) {
//滾動開始
print('Scroll Start: ${scrollNotification.metrics.pixels}');
}else if (scrollNotification is ScrollUpdateNotification) {
//滾動位置更新
print('Scroll Update: ${scrollNotification.metrics.pixels}');
} else if (scrollNotification is ScrollEndNotification) {
//滾動結束
print('Scroll End: ${scrollNotification.metrics.pixels}');
}
return false;
},
child: CustomScrollView(
controller: _controller,
// 滑動列表 widget
),
)
);
}
回到頂部功能
在新聞列表,或者列表數據很多的時候,我們往後翻好幾頁之後,突然想回到列表的頂部,這時候該如何實現呢?
- 首先,在
initState
方法裏,初始化ScrollController
, - 隨後,在視圖構建方法
build
中,我們將ScrollController
對象與 滾動列表controll
關聯,並在SliverAppBar
添加一個按鈕用於點擊後調用_controller.animateTo
方法返回列表頂部。 - 最後,在
dispose
方法中,我們對ScrollController
進行了資源釋放。
實現源碼如下:
class _CustomScrollViewPageState extends State<CustomScrollViewPage> {
//滑動控制器
ScrollController _controller;
@override
void initState() {
//初始化控制器
_controller = ScrollController();
super.initState();
}
@override
void dispose() {
//銷燬控制器
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Material(
child: CustomScrollView(
controller: _controller,
slivers: <Widget>[
//AppBar,包含一個導航欄
SliverAppBar(
pinned: true,
expandedHeight: 250.0,
actions: <Widget>[
RaisedButton(
child: Text('返回頂部'),
onPressed: (){
_controller.animateTo(.0, duration: Duration(milliseconds: 200), curve: Curves.ease);
},
)
],
flexibleSpace: FlexibleSpaceBar(
title: const Text('CustomScrollView'),
background: Image.network(
"https://ssyerv1.oss-cn-hangzhou.aliyuncs.com/picture/389e31d03d36465d8acd9939784df6f0.jpg!sswm", fit: BoxFit.cover,),
),
),
//List
new SliverFixedExtentList(
itemExtent: 50.0,
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
//創建列表項
return new Container(
alignment: Alignment.center,
color: Colors.lightBlue[100 * (index % 9)],
child: new Text('list item $index'),
);
},
childCount: 50 //50個列表項
),
),
],
),
);
}
}
完~