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)

flutter 滚动的基石 Scrollable_Flutter

flutter 滚动的基石 Scrollable_Android_02

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 用来描述滚动的详情的。

  1. pixels 当前滚动距离
  2. minScrollExtent 滚动的最小值
  3. maxScrollExtent 滚动的最大值
  4. viewportDimension viewport 的高度(和滚动方向致)
  5. outOfRange 起出滚动范围,这个是一计算值
bool get outOfRange => pixels < minScrollExtent || pixels > maxScrollExtent;
  1. atEdge 是否在边缘,是一个计算值。
bool get atEdge => pixels == minScrollExtent || pixels == maxScrollExtent;
  1. extentBefore 在viewport 上面超出的高度,是一个计算值。
double get extentBefore => math.max(pixels - minScrollExtent, 0.0);
  1. extentInside viewport 内部的高度,是一个计算值
  2. extentAfter viewport 下面的还没有进入的高度,是一个计算值。
double get extentAfter => math.max(maxScrollExtent - pixels, 0.0);

到这里就结束了,谢谢观看。基础的部分会很枯燥,但对于后面的学习非常重要,还是要打好基础。