版權聲明:本文為作者原創書籍。轉載請注明作者和出處,未經授權,嚴禁私自轉載,侵權必究!!!
情感語錄: 如果你是對的,你沒必要發脾氣;如果你是錯的,你沒資格去發脾氣。這才是真正的智慧。
歡迎來到本章節,上一章節我們講了常用表單
的使用,知識點回顧 戳這里 Flutter基礎第九章
本章節主要講解可以滾動的組件,很多時候一個頁面內容會相當多;當組件內容超過當前顯示視圖范圍時,如果沒有特殊處理,Flutter 則會提示Overflow錯誤;在第二章中我們有講到 ListView
、GridView
可以實現滾動效果,利用這兩個組件可以規避這類問題。但是有些復雜的界面交互效果,使用它們可能就變得非常不友好了。假如有一個頁面,頂部需要一個GridView,底部需要一個ListView,而要求整個頁面的滑動效果是統一的,即它們看起來是一個整體。此時如果使用GridView+ListView來實現的話,就不能保證一致的滑動效果,因為它們的滾動效果是分離的,所以這時就需要一個"膠水",把這些彼此獨立的可滾動組件"粘"起來。
本章簡要:
1、Scrollable 組件
2、Scrollbar 、CupertinoScrollbar 組件
3、SingleChildScrollView、 CustomScrollView 組件
4、SliverList、SliverFixedExtentList、 SliverGrid 組件
5、SliverPadding 、SliverAppBar 組件
6、ScrollController 滾動監聽和控制
一、Scrollable 組件
可滾動組件都直接或間接包含一個Scrollable 組件,比如我們之前學的ListView
、GridView
組件,而他們的父類 BoxScrollView
是繼承與ScrollView
實現的,而 ScrollView中就包含了 Scrollable 組件。 所以可滾動類組件包括一些共同的屬性:
Scrollable構造函數:
Scrollable({
...// 去除部分屬性
this.axisDirection = AxisDirection.down,
this.controller,
this.physics,
@required this.viewportBuilder, //進階篇講
})
axisDirection :
滾動方向。
physics :
此屬性接受一個ScrollPhysics
類型的對象,Flutter會根據具體平臺分別使用不同的ScrollPhysics對象,應用不同的顯示效果:
ClampingScrollPhysics:Android下微光效果。
BouncingScrollPhysics:iOS下彈性效果。
controller:
此屬性接受一個ScrollController對象。ScrollController的主要作用是控制滾動位置和監聽滾動事件。
二、Scrollbar 、 CupertinoScrollbar 組件
Scrollbar是一個Material 風格的滾動條,如果要給可滾動組件添加滾動條,只需將 Scrollbar作為可滾動組件的任意一個父級組件即可,如:
Scrollbar(
child: SingleChildScrollView(
...
),
);
CupertinoScrollbar 是iOS風格的滾動條,如果你使用的是Scrollbar,那么在iOS平臺它會自動切換為CupertinoScrollbar。
三、SingleChildScrollView、 CustomScrollView 組件
1、SingleChildScrollView 組件
SingleChildScrollView類似于Android中的 ScrollView,它只能接收一個子組件。它是繼承 StatelessWidget 實現的,并非 繼承 ScrollView。需要注意的是,通常SingleChildScrollView 只應在期望的內容不會超過屏幕太多時使用,因為SingleChildScrollView不支持基于Sliver的延遲實例化模型,所以如果預計視圖可能包含超出屏幕尺寸太多的內容時,那么使用SingleChildScrollView將會 非常耗性能,此時應該使用一些支持Sliver延遲加載的可滾動組件,如: ListView。
構造函數:
const SingleChildScrollView({
Key key,
this.scrollDirection = Axis.vertical,
this.reverse = false,
this.padding,
bool primary,
this.physics,
this.controller,
this.child,
this.dragStartBehavior = DragStartBehavior.start,
})
除了上面的通用屬性外,它下面的兩個屬性也非常有用:
reverse
:該屬性表示是否反向,就是說初始滾動位置是在“頭”還是“尾”,取false時,初始滾動位置在“頭”,反之則在“尾”,默認為false。
primary
:指是否使用widget樹中默認的 PrimaryScrollController;當滑動方向為垂直方向(scrollDirection值為Axis.vertical)并且沒有指定controller時,primary默認為true.
簡單運用:
import 'package:flutter/material.dart';
class ScrollerViewPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Text("ScrollerViewPage"),
),
body: Container(
child: SingleChildScrollView(
reverse: false,
child: SingleChildScrollViewDemo()))));
}
}
class SingleChildScrollViewDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
List<Widget> listWidget = new List();
for (int i = 0; i < 40; i++) {
var text = Text("Item$i",
style: TextStyle(fontSize: 16, color: Colors.redAccent));
listWidget.add(text);
listWidget.add(Divider());
}
return Column(children: listWidget);
}
}
效果如下:
從效果可以看出 SingleChildScrollView 實現了 ListView 的滾動效果,但是需要注意的時 SingleChildScrollView 包裹的內容不能太多,否則引起性能問題。
2、CustomScrollView 組件
默認場景下,Scalfold 的導航欄都是固定寫死的,如果要做一些交互性或者是沉浸式的交互比較困難。Flutter 提供了 CustomScrollView 來幫助實現跟隨列表滑動發生一些變化的 AppBar 效果。CustomScrollView 繼承自 ScrollView,它可以完成 SingleChildScrollView 能完成的工作,且 CustomScrollView 是可滾動模型。
構造函數:
const CustomScrollView({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
Key center,
double anchor = 0.0,
double cacheExtent,
this.slivers = const <Widget>[],
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
})
除了上面講過的幾個屬性外,下面來看幾個其他重要的屬性。
shrinkWrap
配置可控制 AppBar 下的內容在滾動時,是否可以超過 AppBar 的邊界。如果為true,則滑動內容區域 可覆蓋 AppBar 直到屏幕頂端。如果 為 false ,則只能在 AppBar 以下區域滑動,即 AppBar 始終在頂部顯示。
anchor
該屬性不太好描述,它的的取值范圍值0.0-1.0
之間,這個屬性迫使組件將其自身定位在父組件中的某個相對或絕對位置。后面演示觀察下。
slivers
List<Widget> 類型,用來承載滑動內容
四、 SliverList、SliverFixedExtentList、 SliverGrid 組件
1、 這三個組件你可以理解是 Sliver版 (ListView(SliverList、SliverFixedExtentList)、GridView(SliverGrid))組件。SliverList 就是一個 ListView,只不過在整個實現上,需要指定一個 delegate,比如通過 SliverChildBuilderDelegate 進行列表的構建。
SliverList(
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
//創建列表項
return new Container(
alignment: Alignment.center,
child: new Text('list item $index'),
);
},
childCount: 50 //50個列表項
),
),
SliverFixedExtentList
你可以看做是 SliverList的加強版,它可以使用 itemExtent
屬性來控制 item 的范圍(高度)。
2、SliverGrid 如同 GridView 一樣,只不過在整個實現上,需要指定一個 delegate 和 gridDelegate,比如通過 SliverChildBuilderDelegate 進行列表的構建,SliverGridDelegateWithFixedCrossAxisCount 來控制列數和行間距和列間距
SliverPadding(
padding: EdgeInsets.all(8.0),
sliver: SliverGrid( //Grid
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, //Grid按兩列顯示
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 4.0,
),
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
//創建子widget
return new Container(
alignment: Alignment.center,
color: Colors.cyan[100 * (index % 9)],
child: new Text('grid item $index'),
);
},
childCount: 20,
),
),
),
五、SliverPadding 、SliverAppBar 組件
1、SliverPadding 組件同 前面講的 Padding 組件是一樣的,只不過 SliverPadding 接收的子組件是 sliver Widget。
const SliverPadding({
Key key,
@required this.padding,
Widget sliver,
})
2、SliverAppBar 組件 是專門服務于 CustomScrollView 的; SliverAppBar 支持的屬性和 AppBar 支持的屬性基本無異,SliverAppBar 因為需要跟隨 ScrollView 的一些操作,屬性會多一些。
構造函數:
const SliverAppBar({
Key key,
this.leading,
this.automaticallyImplyLeading = true,
this.title,
this.actions,
this.flexibleSpace,
this.bottom,
this.elevation,
this.forceElevated = false,
this.backgroundColor,
this.brightness,
this.iconTheme,
this.actionsIconTheme,
this.textTheme,
this.primary = true,
this.centerTitle,
this.titleSpacing = NavigationToolbar.kMiddleSpacing,
this.expandedHeight,
this.floating = false,
this.pinned = false,
this.snap = false,
this.shape,
})
除開和AppBar 中屬性外,下面來看幾個重要屬性:
floating:
false 表示當列表往下滑動時,會先將列表內容滾動到頂部,然后再將 SliverAppBar 浮動出現,true表示當列表往下滑動時,會先將 SliverAppBar 浮動出現(與列表是否滾動到頂部無關),然后再繼續列表的滑動。
pinned
屬性能夠決定是否將導航欄部分固定。true 表示導航欄不會完全消失,否則隨滾動逐漸消失。
expandedHeight
屬性可以配置AppBar 展開后的高度。
flexibleSpace
屬性可以定制 AppBar 展開后的樣式,結合 FlexibleSpaceBar
控制。
下面來綜合運用下,這幾個新組建:
import 'package:flutter/material.dart';
class ScrollerViewPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CustomScrollViewDemo();
}
}
class CustomScrollViewDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Material(
color: Colors.green,
child: CustomScrollView(
//距離 AppBar 百分之10 高度
anchor: 0.1,
slivers: <Widget>[
//AppBar,包含一個導航欄
SliverAppBar(
pinned: true,
expandedHeight: 200.0,
flexibleSpace: FlexibleSpaceBar(
title: Text('ScrollerViewPage'),
//centerTitle: true,
background: Image.asset(
"images/mm.jpg",
fit: BoxFit.cover,
),
),
),
SliverPadding(
padding: EdgeInsets.all(8.0),
sliver: SliverGrid(
//Grid
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, //Grid按兩列顯示
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 4.0,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
//創建子widget
return Container(
alignment: Alignment.center,
color: Colors.red[100 * ((index + 2) % 9)],
child: Text('grid item $index'),
);
},
childCount: 20,
),
),
),
//List
SliverFixedExtentList(
itemExtent: 50.0,
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
//創建列表項
return Container(
alignment: Alignment.center,
color: Colors.lightBlue[100 * ((index + 2) % 9)],
child: Text('list item $index'),
);
}, childCount: 50 //50個列表項
),
),
],
),
);
}
}
效果如下:
可以看出 在設置 anchor
屬性后 底部的 SliverGrid 和 SliverAppBar 之間產生了整個父容器的百分之10距離,且上滑動時 SliverGrid 滾動超過這段距離后才會把 SliverAppBar 往上頂。下滑時 SliverAppBar 完全展開后才逐漸顯示這段距離。
六、ScrollController 滾動監聽和控制
本章和前面章節講解的 ListView 、 GridView;都是可滾動組件,在開發中很多時候需要監聽 這些可滾動組件的滾動位置信息,比如,滾動到某一個距離后要顯示一個視圖,再或者需要從某一位置需要跳到另一個位置時等。在 Flutter 中 ScrollController 能幫助我們解決這類問題。
構造函數:
ScrollController({
double initialScrollOffset = 0.0,
this.keepScrollOffset = true,
this.debugLabel,
})
ScrollController 中常用的屬性和方法:
initialScrollOffset :
初始滾動位置。
keepScrollOffset :
是否保存滾動位置。
addListener() :
添加滾動位置信息監聽。
removeListener() :
刪除滾動位置信息監聽。
offset:
返回滑動距離的像素單位值。
dispose():
銷毀 ScrollController 控制器。
jumpTo():
跳轉到某一位置,不帶動畫。
animateTo():
跳轉到某一位置,帶動畫效果,Curves
給我們提供了 幾十種動畫效果可供選擇。
下面來將上面的例子進行改造下,我需要監聽 CustomScrollView 的滑動信息,當滑動距離超過500個像素后 我要在屏幕上顯示一個回到頂部的按鈕,然后點擊按鈕直接回到頂部去。這里面設計到界面模板的刷新 需要繼承 StatefulWidget 來改造,且需要結合前面學的 FloatingActionButton 組件去實現(如果忘了,請回顧前面知識點)
import 'package:flutter/material.dart';
class ScrollerViewPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CustomScrollViewDemo();
}
}
class CustomScrollViewDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Material(
child: ScrollListenerDemo()
);
}
}
class ScrollListenerDemo extends StatefulWidget {
@override
_ScrollListenerDemoState createState() => _ScrollListenerDemoState();
}
class _ScrollListenerDemoState extends State<ScrollListenerDemo> {
ScrollController _controller = new ScrollController();
//是否顯示“返回到頂部”按鈕
bool showToTopBtn = true;
@override
void initState() {
//監聽滾動事件,打印滾動位置
_controller.addListener(() {
print(_controller.offset); //打印滾動位置
if (_controller.offset < 500 && showToTopBtn) {
setState(() {
showToTopBtn = false;
});
} else if (_controller.offset >= 500 && showToTopBtn == false) {
setState(() {
showToTopBtn = true;
});
}
});
}
@override
void dispose() {
//為了避免內存泄露,需要調用_controller.dispose
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: !showToTopBtn ? null : FloatingActionButton(
backgroundColor: Colors.deepPurple,
child: Icon(Icons.arrow_upward),
onPressed: () {
//返回到頂部時執行動畫
_controller.animateTo(0.0,
duration: Duration(milliseconds: 1000),
curve: Curves.easeInBack
);
}
),
body: CustomScrollView(
controller: _controller,
slivers: <Widget>[
//AppBar,包含一個導航欄
SliverAppBar(
pinned: true,
expandedHeight: 200.0,
flexibleSpace: FlexibleSpaceBar(
title: Text('ScrollerViewPage'),
//centerTitle: true,
background: Image.asset(
"images/mm.jpg",
fit: BoxFit.cover,
),
),
),
SliverPadding(
padding: EdgeInsets.all(8.0),
sliver: SliverGrid(
//Grid
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, //Grid按兩列顯示
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 4.0,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
//創建子widget
return Container(
alignment: Alignment.center,
color: Colors.red[100 * ((index + 2) % 9)],
child: Text('grid item $index'),
);
},
childCount: 20,
),
),
),
//List
SliverFixedExtentList(
itemExtent: 50.0,
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
//創建列表項
return Container(
alignment: Alignment.center,
color: Colors.lightBlue[100 * ((index + 2) % 9)],
child: Text('list item $index'),
);
}, childCount: 50 //50個列表項
),
),
],
)
);
}
}
效果如下:
本章節的東西不多,但是值得去研究的內容還有很多,更多高級用法還需自己去探索。
好了本章節到此結束,又到了說再見的時候了,如果你喜歡請留下你的小紅星,你們的支持才是創作的動力,如有錯誤,請熱心的你留言指正, 謝謝大家觀看,下章再會 O(∩_∩)O