Scrollable 是一个 widget,提供滚动的功能。虽然很少会直接使用 Scrollable,但是掌握 Scrollable, 对掌握使用它的 ListView 等组件会有极大的帮助。
我们先用 Scrollable 举一个例子,让内容滚动起来。
MaterialApp(home: SafeArea(
child: Scaffold(body: Scrollable(viewportBuilder: (context, position) {
return Viewport(offset: position, slivers: [
SliverList(delegate: SliverChildBuilderDelegate(
(context, index) {
return ListTile(
title: Text('$index'),
);
},
)),
]);
}))))
在这个基础上,我们可以添加修改 Scrollable 的各种参数看一下效果。
- axisDirection 滚动的方向。 默认值是 AxisDirection.down。
- controller 滚动控制器。用来控制滚动的位置。
- physics 规定 Scrollable widget 的 physics。比如规定当滚动超出范围时的行为,
- viewportBuilder 返回一个 viewport。
- incrementCalculator 用键盘操作滚动的时候,用它来计算滚动距离
- restorationId 用来恢复滚动位置的 key。只有意外退出才会有恢复功能,主动退出不恢复。
- scrollBehavior 规定 Scrollable widget 的行为。
- clipBehavior 剪裁内容,默认是 Clip.hardEdge
physics
我们先看一下 physics 的属性。有的属性我们一般是不用到的,但对于读懂源代码是很有用的。
allowImplicitScrolling
当执行 RenderObject.showOnScreen 的时候,是否允许滚动。
举个例子:在一个滚动 widget中 原本一个文本框是不可见的,当获得焦点的时候,如果 allowImplicitScrolling is true,会发生滚动,让 文本框能完全显示。 、
dragStartDistanceMotionThreshold
这是一个f阈值,拖动多少距离触发滚动。如果 是 null ,就没有任何限制。
maxFlingVelocity
这是一个限定值,滚动速度不要太快。
minFlingDistance
最小滚动距离。要么不动,要滚动就至少是这个距离。
minFlingVelocity
最小滚动速度。
parent
合并其它类型 physics。
const BouncingScrollPhysics(parent:const AlwaysScrollableScrollPhysics())
方法 applyTo 也有同样的效果
var physics = BouncingScrollPhysics();
physics.applyTo(const AlwaysScrollableScrollPhysics())
physics 的值获取过程是这样的。优先取 scrollable 的属性,如果为 null 取 scrollBehavior 的 physics。如果 scrollBehavior 也为 null 取 ScrollConfiguration 的值。(这段需要再看下代码)
viewport
scrollable 提供滚动功能,viewport 负责展示内容。下面解说一下 viewport 的参数
- axisDirection 默认值 AxisDirection.down 内容的伸展方向是由上到下。这个值一般和 scrollable 的 axisDirection 需要保持一致。
- crossAxisDirection 和 AxisDirection 垂直的方向。
- anchor 在没有滚动的情况下,内容距离起始边界的距离, 默认值 0。取值范围[0,1],比如取值 0.5 内容距离起始边界 50%的距离。
- offset 指导哪些内容显示出来。
- slivers 滚动的内容。
- center 是一个 key。如果 center 不为 null ,滚动内容 slivers 是一个 list。slivers 中必须有一个 直属 item 的 key 和 center 相等。这个 item 会被显示在距离起始边界为 0 的位置。
- cacheExtent 缓存的距离。有两种模式 CacheExtentStyle.pixel,CacheExtentStyle.viewport
- cacheExtentStyle 指定缓存模式
举一个 center的例子。
center is null | center is ValueKey(1) |
var key =const ValueKey(1);
MaterialApp(home: SafeArea(
child: Scaffold(body: Scrollable(viewportBuilder: (context, position) {
return Viewport(center: key, offset: position, slivers: [
SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return ListTile(
title: Text('$index'),
);
}, childCount: 5),
),
SliverToBoxAdapter(
key: key,
child: Container(
height: 100,
color: Colors.blue,
),
),
SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return ListTile(
title: Text('$index'),
);
}, childCount: 12),
),
]);
}))
));
scrollBehavior
scrollBehavior 是用来描述滚动行为的。scrollBehavior 可以直接给 scrollable赋值,也可以由 ScrollConfiguration 来影响它的 subtree。ScrollConfiguration 是 inheritWidget,它有 scrollBehavior 属性,所以 subtree 都可以得到 scrollBehavior。
为了自定义滚动行为,你需要新建 class extends from scrollBehavior。举个自定义 physics 和 overscrollIndicator 的例子。 用了 MyScrollBehavior 后,无论内容多少,都可以滚动,overscroll 不再有提示。原来 android 在 overflow 的时候会出现的蓝色波浪提示就不会出现了。
class MyScrollBehavior extends ScrollBehavior {
@override
ScrollPhysics getScrollPhysics(BuildContext context) {
return const AlwaysScrollableScrollPhysics();
}
@override
Widget buildOverscrollIndicator(
BuildContext context, Widget child, ScrollableDetails details) {
return const SizedBox.shrink();
}
}
用的时候可以直接在 scrolable 构造函数中传入,也可以放在 ScrollConfiguration 中影响 subtree.
监听滚动行为
Scrollable Widget 在滚动的时候,会发滚动事件出来,用 NotificationListener 来监听。滚动事件分下面几种
- ScrollStartNotification 表明组件开始滚动。
- ScrollUpdateNotification 组件改变的滚动位置。
- UserScrollNotification 改变滚动方向。
- ScrollEndNotification 滚动结束。
上面四个都是 ScrollNotification 的子类,还有一个不是它的子类,但也会用到 当 viewport 的尺寸改变的时候,会发出 ScrollMetricsNotification 。
NotificationListener<ScrollNotification>(
onNotification: (notification) {
//metrics 包含了滚动的详情
print(notification.metrics);
//如果不是拖动触发 dragDetails is null. 程序也可以触发滚动。
print(notification.dragDetails);
//按事件类型做出响应
if(notification is ScrollStartNotification){
...
}
//继续向上层冒泡
return false;
},
child: Scrollable(
...
metrics 详情
ScrollMetrics 用来描述滚动的详情的。
- pixels 当前滚动距离
- minScrollExtent 滚动的最小值
- maxScrollExtent 滚动的最大值
- viewportDimension viewport 的高度(和滚动方向致)
- outOfRange 起出滚动范围,这个是一计算值
bool get outOfRange => pixels < minScrollExtent || pixels > maxScrollExtent;
- atEdge 是否在边缘,是一个计算值。
bool get atEdge => pixels == minScrollExtent || pixels == maxScrollExtent;
- extentBefore 在viewport 上面超出的高度,是一个计算值。
double get extentBefore => math.max(pixels - minScrollExtent, 0.0);
- extentInside viewport 内部的高度,是一个计算值
- extentAfter viewport 下面的还没有进入的高度,是一个计算值。
double get extentAfter => math.max(maxScrollExtent - pixels, 0.0);
到这里就结束了,谢谢观看。基础的部分会很枯燥,但对于后面的学习非常重要,还是要打好基础。