当前位置:   article > 正文

【Flutter之旅】可滚动组件介绍_flutter scrollnotification 滑动方向

flutter scrollnotification 滑动方向

可滚动组件介绍

环境介绍以及参考文献

本示例是在 Linux 16.04.1-Ubuntu 搭配 VS Code 使用。

《Flutter实战》电子书
Flutter中文网

可滚动组件简介

当组件内容超过当前显示视口(ViewPort)时,如果不做处理,Flutter 会提示 Overflow 错误。

针对 overflow 问题,flutter 提供了可滚动组件去显示长列表和长布局。

可滚动组件的核心组件 Scrollable

Scrollable({
  //...
  this.axisDirection = AxisDirection.down, // 滚动方向
  this.controller, // 接受 ScrollController 对象,控制滚动位置和监听滚动事件
  this.physics, // 接受 ScrollPhysics 对象,响应用户操作。
  @required this.viewportBuilder, // 后面介绍
})

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

基于Sliver的延迟构建

因为可滚动组件的子组件可能会非常多,如果一次性将全部子组件构建出来会非常耗费内存。因此 Flutter 提出一个 Sliver 的概念,一个可滚动组件如果支持 Sliver 模型,那么该滚动可以将子组件分成多个 Sliver, 只有当 Sliver 出现在 ViewPort 时才去构建它。

SingleChildScrollView

类似于 Android 中的 ScrollView。不支持 Sliver 的延迟实例化模型,因此只适合不太多的内容。

class SingleChildScrollViewTestRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    return Scrollbar( // 显示进度条
      child: SingleChildScrollView(
        padding: EdgeInsets.all(16.0),
        child: Center(
          child: Column( 
            //动态创建一个List<Widget>  
            children: str.split("") 
                //每一个字母都用一个Text显示,字体为原来的两倍
                .map((c) => Text(c, textScaleFactor: 2.0,)) 
                .toList(),
          ),
        ),
      ),
    );
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

ListView

可以沿一个方向线性排布所有子组件,支持 Sliver 的延迟实例化模型。

实现一个可以自加载数据的 listview,并设置 listview 的标题,在 listview 滚动的时候其标题一直置顶。
请添加图片描述

class ListViewRoute extends StatefulWidget {
  
  @override
  _ListViewState createState() => new _ListViewState();
  
}

class _ListViewState extends State<ListViewRoute> {
  static const loadingTag = "##loading##";
  var _words = <String>[loadingTag];

  void _retrieveData() {
    print("_retrieveData");
    Future.delayed(Duration(seconds: 2)).then((e) {
      setState(() {
		    _words.insertAll(_words.length - 1,
          generateWordPairs().take(20).map((e) => e.asPascalCase).toList()
      	);
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    Widget divider1 = Divider(color: Colors.blue,);
    Widget divider2 = Divider(color: Colors.green);
    return Scaffold(
      appBar: AppBar(
        title: Text("ListView"),
      ),
      body: Column(
        children: <Widget>[
          Container (
            decoration: BoxDecoration (
                color: Colors.purple,
            ),
            child: ListTile(title:Text("Word_List"),
              trailing: Icon(Icons.keyboard_arrow_right),
              leading: Icon(Icons.list_alt),
              onTap: () => print(_words.length),),
          ),
          Expanded(
            child: ListView.separated(
              itemCount: _words.length,
              itemBuilder: (context, index) {
                if(_words[index] == loadingTag) {
                  if(_words.length - 1 < 100 ) {
                    _retrieveData();
                    return Container(
                      padding: const EdgeInsets.all(16.0),
                      alignment: Alignment.center,
                      child: SizedBox(
                        width: 24.0,
                        height: 24.0,
                        child: CircularProgressIndicator(strokeWidth: 2.0,),
                      ),
                    );
                  } else {
                    return Container(
                      alignment: Alignment.center,
                      padding: EdgeInsets.all(16.0),
                      child: Text("没有更多了", style: TextStyle(color: Colors.grey),)
                    );
                  }
                }
                return ListTile(title: Text(_words[index]));
              },
              separatorBuilder: (context, index) {
                return index % 2 == 0 ? divider1 : divider2;
              } 
            ),
          )
        ],
      )
    );
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

GridView

利用 GridView 可以构建一个二维网格列表。

请添加图片描述

class GridViewRoute extends StatefulWidget {
  @override
  _GridViewState createState() => new _GridViewState();
}

class _GridViewState extends State<GridViewRoute> {
  
  List<IconData> _icons = [];

  @override
  void initState() {
    super.initState();
    _retrieveIcons();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("GridView"),
      ),
      body: GridView.builder(
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          childAspectRatio: 1.0,
        ),
        itemCount: _icons.length,
        itemBuilder: (context, index) {
          if(index == _icons.length - 1 && _icons.length < 200) {
            _retrieveIcons();
          }
          return Icon(_icons[index]);
        },
      ),
    );
  }

  void _retrieveIcons() {
    Future.delayed(Duration(milliseconds: 200)).then((e) {
      setState(() {
        _icons.addAll([
          Icons.ac_unit,
          Icons.airport_shuttle,
          Icons.all_inclusive,
          Icons.beach_access,
          Icons.cake,
          Icons.free_breakfast,
        ]);
      });
    });
  }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

CustomScrollView

CustomScrollView 可以将多个 Sliver 合在一起,这些 Sliver 公用 CustomScrollView 的 Scrollable,实现统一的滑动效果。
请添加图片描述

class CustomScrollViewRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Material(
      child: CustomScrollView(
        slivers: <Widget>[
          //AppBar,包含一个导航栏
          SliverAppBar(
            pinned: true,
            expandedHeight: 250.0,
            flexibleSpace: FlexibleSpaceBar(
              title: const Text('Demo'),
            ),
          ),

          SliverPadding(
            padding: const EdgeInsets.all(8.0),
            sliver: new SliverGrid( //Grid
              gridDelegate: new 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: 10,
              ),
            ),
          ),
          //List
          new SliverFixedExtentList(
            itemExtent: 50.0,
            delegate: new SliverChildBuilderDelegate(
                    (BuildContext context, int index) {
                  //创建列表项 
                  return new Container(
                    alignment: Alignment.center,
                    color: Colors.lightBlue[100 * (index % 9)],
                    child: new Text('list item $index'),
                  );
                },
                childCount: 10
            ),
          ),
        ],
      ),
    );
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

滚动监听及控制

ScrollController

控制可滚动组件的滚动位置

//新建一个 ScrollController 工具类
class ScrollControllerUtil {

  ScrollController _controller = new ScrollController();

  //在 init 的时候 register
  void init(String tag) {
    _controller.addListener(() {
        print(tag + " : $_controller.offset");
    });
  }

  //不需要的时候 dispose,避免内存泄漏
  void dispose() {
    _controller.dispose();
  }

  ScrollController getController() {
    return _controller;
  }
}

//使用
//在 gridview 或者 listview 的 control 属性中设置 ScrollController
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

NotificationListener

  • 通过 NotificationListener 可以在从可滚动组件到 widget 树根之间任意位置都能监听。而 ScrollController 只能和具体的可滚动组件关联后才可以。
  • 收到滚动事件后获得的信息不同;NotificationListener 在收到滚动事件时,通知中会携带当前滚动位置和 ViewPort 的一些信息,而 ScrollController 只能获取当前滚动位置。
NotificationListener<ScrollNotification>(
        onNotification: (ScrollNotification notification) {
          double progress = notification.metrics.pixels /
              notification.metrics.maxScrollExtent;
          //重新构建
          setState(() {
            _progress = "${(progress * 100).toInt()}%";
          });
          print("BottomEdge: ${notification.metrics.extentAfter == 0}");
          return true;
        },
);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/494018
推荐阅读
相关标签
  

闽ICP备14008679号