IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 【Flutter之旅】可滚动组件介绍 -> 正文阅读

[移动开发]【Flutter之旅】可滚动组件介绍

可滚动组件介绍

环境介绍以及参考文献

本示例是在 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, // 后面介绍
})

基于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(),
          ),
        ),
      ),
    );
  }
}

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;
              } 
            ),
          )
        ],
      )
    );
  }
}

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,
        ]);
      });
    });
  }
}

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
            ),
          ),
        ],
      ),
    );
  }
}

滚动监听及控制

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

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;
        },
);
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-10 13:32:03  更:2021-08-10 13:33:07 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/18 23:54:34-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码