flutter 可滚动wdiget 滚动监听及控制

1.ScrollController

ScrollController({

  double initialScrollOffset = 0.0, //初始滚动位置

  this.keepScrollOffset = true,//是否保存滚动位置

  // 如果ScrollController.keepScrollOffset为false,则滚动位置将不会被存储,Scrollable Widget重新创建时会使用ScrollController.initialScrollOffset;
  // ScrollController.keepScrollOffset为true时,Scrollable Widget在第一次创建时,会滚动到initialScrollOffset处,
  // 因为这时还没有存储过滚动位置。在接下来的滚动中就会存储、恢复滚动位置,而initialScrollOffset会被忽略
  // tab页面切换时候,ListView会保存位置

  ...
})

2.ScrollController常用的属性和方法

offset:可滚动Widget当前滚动的位置

jumpTo(double offset)animateTo(double offset,...):这两个方法用于跳转到指定的位置,它们不同之处在于,后者在跳转时会执行一个动画,而前者不会

3.滚动监听

controller.addListener(()=>print(controller.offset))

4.例子:当滚动位置发生变化时,然后判断当前位置是否超过500像素,如果超过则在屏幕右下角显示一个“返回顶部”的按钮,该按钮点击后可以使ListView恢复到初始位置;如果没有超过500像素,则隐藏“返回顶部”按钮

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';

class MyList extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return _MyList();
  }
}

class _MyList extends State<MyList> {
  List _list = ["loading"];

  //自定义的scrollController

  ScrollController _scroll = new ScrollController();

  bool isOver = false;

  bool hasTop = false;  //是否展示返回顶部按钮

  @override
  void initState() {
    super.initState();
    _retrieveData();
    _scroll.addListener(() {
      if (_scroll.position.pixels >= _scroll.position.maxScrollExtent &&
          !isOver) {
        _retrieveData();
      }
      //超过500,就显示返回顶部按钮,小于500就不展示

      if (_scroll.offset >= 500 && !hasTop) {
        setState(() {
          hasTop = true;
        });
      } else if (_scroll.offset < 500 && hasTop) {
        setState(() {
          hasTop = false;
        });
      }
    });
  }

  @override
  void dispose() {
    _scroll.dispose();
    super.dispose();
  }

  Widget _getList() {
    return Column(
      children: <Widget>[
        Expanded(
          flex: 1,
          child: ListView.builder(
            controller: _scroll,  //scroll的配置
            itemCount: _list.length,
            itemExtent: 60.0,
            itemBuilder: (context, index) {
              if (_list[index] == "loading") {
                if (_list.length >= 50) {
                  isOver = true; //是否已经结束
                  return Row(
                    children: <Widget>[Text("没有更多了")],
                    mainAxisAlignment: MainAxisAlignment.center,
                  );
                } else {
                  return Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      SizedBox(
                          width: 24.0,
                          height: 24.0,
                          child: CircularProgressIndicator(strokeWidth: 2.0)),
                    ],
                  );
                }
              }
              return ListTile(
                title: Text(_list[index]),
              );
            },
          ),
        )
      ],
    );
  }

  void _retrieveData() {
    Future.delayed(Duration(seconds: 2)).then((e) {
      setState(() {
        _list.insertAll(
            _list.length - 1,
            //每次生成20个单词
            generateWordPairs().take(100).map((e) => e.asPascalCase).toList());
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text("list"),
      ),
      body: _getList(),
      //返回顶部按钮
      floatingActionButton: hasTop
          ? FloatingActionButton(
              child: Icon(Icons.arrow_upward),
              onPressed: () {
                _scroll.animateTo(.0,
                    duration: Duration(milliseconds: 300), curve: Curves.ease);
              },
            )
          : null,
    );
  }
}

     

5.keepScrollOffset的使用

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';

class MyBar extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return _MyBar();
  }
}

class _MyBar extends State<MyBar> with SingleTickerProviderStateMixin {
  List _tabs = ["首页", "新闻", "城市"];

  List _tem = [];

  List _scrollList = [
    new ScrollController(), //默认keepScrollOffset是true,记录滚动位置
    new ScrollController(),
    new ScrollController()
  ];

  TabController _tb;

  void initState() {
    super.initState();
    _tb = new TabController(length: _tabs.length, vsync: this);
    _tem.addAll(
        generateWordPairs().take(50).map((e) => e.asPascalCase).toList());
  }

  List<ListView> _getContent() {
    var tabs =new List<ListView>();
    for (int i = 0; i < _tabs.length; i++) {
      tabs.add(ListView.builder(
        key: PageStorageKey(i+1),
        controller: _scrollList[i],
        itemCount: _tem.length,
        itemExtent: 50.0,
        itemBuilder: (context, index) {
          return Container(
            alignment: Alignment.center,
            child: Text("${_tabs[i]}--${index + 1}----${_tem[index]}"),
          );
        },
      ));
    }
    print(tabs);

    return tabs.toList();
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text("tab-list"),
        centerTitle: true,
        bottom: TabBar(
            controller: _tb,
            tabs: _tabs
                .map((txt) => Tab(
                      text: txt,
                    ))
                .toList()),
      ),
      body: TabBarView(
        controller: _tb,
        children: _getContent(),
      ),
    );
  }
}

      

首页滑动到新闻,在返回首页的时候,首页会记录之前的位置。

如果首页的ListView的 keepScrollOffset为false,就不会记录位置,在返回首页的时候还是在最开始1的位置

6.NotificationListener

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

在接收到滚动事件时,参数类型为ScrollNotification,它包括一个metrics属性,它的类型是ScrollMetrics,该属性包含当前ViewPort及滚动位置等信息

 

 

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

class NocaScroll extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return _NocaScroll();
  }
}

class _NocaScroll extends State<NocaScroll> {
  String _progress = "0%";

  Widget _getScroll() {
    return Scrollbar(
      child: NotificationListener<ScrollNotification>(
        onNotification: (ScrollNotification notification) {
          double pr = notification.metrics.pixels /
              notification.metrics.maxScrollExtent;
          setState(() {
            _progress = "${(pr * 100).toInt()}%";
          });
          // return true 如果这行展示,就不会出现滚动条
        },
        child: Stack(
          alignment: Alignment.center,
          children: <Widget>[
            ListView.builder(
              itemCount: 100,
              itemExtent: 50.0,
              itemBuilder: (context, i) {
                return ListTile(
                  title: Text("list$i"),
                );
              },
            ),
            CircleAvatar(
              radius: 50.0,
              backgroundColor: Colors.grey,
              child: Text(_progress),
            )
          ],
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text("NocaScroll"),
      ),
      body: _getScroll(),
    );
  }
}

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页