版权声明:本文为作者原创书籍。转载请注明作者和出处,未经授权,严禁私自转载,侵权必究!!!
情感语录: 如果你是对的,你没必要发脾气;如果你是错的,你没资格去发脾气。这才是真正的智慧。
欢迎来到本章节,上一章节我们讲了常用表单
的使用,知识点回顾 戳这里 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