在之前的文章中,每个滚动组件都有一个 controller 属性,它接受一个 ScrollController 对象。

它可以用来控制滚动 Widget 的滚动位置。

构造函数

ScrollController({
  double initialScrollOffset = 0.0,
  this.keepScrollOffset = true,
  this.debugLabel,
})
  • initialScrollOffset,初始滚动位置
  • keepScrollOffset,是否保存滚动位置
  • debugLabel,debug 标签,用于调试

属性和方法

属性

  • offset,当前滚动的位置

  • position,一个 ScrollController 可以同时被多个 Scrollable Widget 使用,ScrollController 会为每一个 Scrollable Widget 创建一个 ScrollPosition 对象,这些 ScrollPosition 保存在 ScrollControllerpositions 属性中(List)。ScrollPosition 是真正保存滑动位置信息的对象,offset 只是一个便捷属性:

    double get offset => position.pixels;
    

    一个 ScrollController 虽然可以对应多个Scrollable Widget,但是有一些操作,如读取滚动位置offset,则需要一对一,但是我们仍然可以在一对多的情况下,通过其它方法读取滚动位置,举个例子,假设一个ScrollController同时被两个Scrollable Widget使用,那么我们可以通过如下方式分别读取他们的滚动位置:

    ...
    controller.positions.elementAt(0).pixels
    controller.positions.elementAt(1).pixels
    ...
    

    我们可以通过 controller.positions.length 来确定 controller 被几个 Scrollable Widget 使用。

  • positions,存储 position 的对象

方法

  • animateTo,跳转到指定的位置, 跳转时会有一个动画效果
  • jumpTo,跳转到指定的位置, 没有动画效果
  • addListener,添加监听器
  • removeListener,移除监听器

示例

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Text Demo',
      theme: ThemeData(primarySwatch: Colors.green),
      home: MyHomePage(title: 'Text Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  ScrollController _controller = new ScrollController();

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _controller.addListener(() {
      print("滚动位置: ${_controller.offset}"); //打印滚动位置
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView.builder(
        controller: _controller,
        itemBuilder: (BuildContext context, int index) {
          return Container(
            alignment: Alignment.center,
            margin: EdgeInsets.fromLTRB(0, 2, 0, 2),
            height: 50,
            width: double.infinity,
            child: Text(
              "${index}",
            ),
            color: Colors.lightGreen,
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        backgroundColor: Colors.deepOrange,
        child: Icon(
          Icons.add,
          color: Colors.white,
        ),
      ),
    );
  }
}

在滚动 ListView 的时候,会打印滚动的距离:

NotificationListener 和 ScrollNotification

NotificationListener 是一个 Widget,模板参数 T 是想监听的通知类型,如果省略,则所有类型通知都会被监听,如果指定特定类型,则只有该类型的通知会被监听。

构造函数

const NotificationListener({
  Key key,
  @required this.child,
  this.onNotification,
})
  • child 就是我们要监听的滚动 Widget

  • onNotification,用于实现监听处理逻辑,该回调可以返回一个布尔值,代表是否阻止该事件继续向上冒泡,如果为 true 时,则冒泡终止,事件停止向上传播,如果不返回或者返回值为 false 时,则冒泡继续。

对于我们监听滚动 Widget 来说,ScrollNotification 就是我们需要监听的通知类型:

NotificationListener<ScrollNotification>(
        onNotification: (scrollNotification) {
          //处理逻辑
        },
        child: ListView.builder(
          //略
        ),
      )

ScrollNotification 是一个抽象类:

abstract class ScrollNotification extends LayoutChangedNotification with ViewportNotificationMixin {
  /// Initializes fields for subclasses.
  ScrollNotification({
    @required this.metrics,
    @required this.context,
  });
}

常用的是它的三个实现类:

具体含义从名称上就可以看出来,不赘述,

ScrollNotification 有一个 ScrollMetrics 类型的属性 metrics,该属性包含当前 ViewPort 及滚动位置等信息:

  • pixels:当前滚动位置。
  • maxScrollExtent:最大可滚动长度。
  • extentBefore:滑出 ViewPort 顶部的长度;此示例中相当于顶部滑出屏幕上方的列表长度。
  • extentInside:ViewPort 内部长度;此示例中屏幕显示的列表部分的长度。
  • extentAfter:列表中未滑入ViewPort 部分的长度;此示例中列表底部未显示到屏幕范围部分的长度。
  • atEdge:是否滑到了 Scrollable Widget 的边界(此示例中相当于列表顶或底部)。

示例

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Text Demo',
      theme: ThemeData(primarySwatch: Colors.green),
      home: MyHomePage(title: 'Text Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  ScrollController _controller = new ScrollController();

  _onStartScroll(ScrollMetrics metrics) {
    print("Scroll Start");
    scrolling = true;
    setState(() {

    });
  }

  _onEndScroll(ScrollMetrics metrics) {
    print("Scroll End");
    scrolling = false;
    setState(() {

    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NotificationListener<ScrollNotification>(
        onNotification: (scrollNotification) {
          if (scrollNotification is ScrollStartNotification) {
            _onStartScroll(scrollNotification.metrics);
          } else if (scrollNotification is ScrollEndNotification) {
            _onEndScroll(scrollNotification.metrics);
          }
          return false;
        },
        child: ListView.builder(
          controller: _controller,
          itemBuilder: (BuildContext context, int index) {
            return Container(
              alignment: Alignment.center,
              margin: EdgeInsets.fromLTRB(0, 2, 0, 2),
              height: 50,
              width: double.infinity,
              child: Text(
                "${index}",
              ),
              color: Colors.lightGreen,
            );
          },
        ),
      ),
      floatingActionButton: updateFloatingActionButton(),
    );
  }

  bool scrolling = false;

  Widget updateFloatingActionButton(){
    if(scrolling){
      return null;
    }else{
      return FloatingActionButton(
        backgroundColor: Colors.deepOrange,
        child: Icon(Icons.add),
      );
    }
  }
}

在上面的例子中,当 ListView 发生滚动的时候,floatingActionButton 会隐藏,当停止滚动时,会显示,效果如下:

results matching ""

    No results matching ""