Flutter项目中的滚动是如何实现的?

3,676 阅读8分钟

前言

自从上一篇博客这样去看Flutter基础和布局就容易多了,带着大家大致熟悉了Flutter基础WidgetFlutter的布局Widget在项目中的简单使用,本篇博客将继续讲述博客篇幅里的Flutter关于滚动Widget【项目中使用最多的】----很重要的撒

  • Flutter滚动Widget - ListView组件
  • Flutter滚动Widget - GridView组件
  • Flutter滚动组合 - Slivers组件
  • Flutter 监听滚动事件

希望大家可以跟着敲敲,通过一两个月的了解Flutter知识点【动手写!动手写!动手写】,肯定可以具备开发水平的!本人会不断提供优质的博客内容给大家,目前涵盖Objective-C、Swift、Flutter、小程序的开发。欢迎点赞博客及关注本人,共同进步是目的撒~~~

ListView组件

移动端数据量比较大时,都是通过列表来进行展示的,比如商品数据、聊天列表、通信录、朋友圈等。 在Android中,可以使用ListView或RecyclerView来实现;在iOS中,可以通过UITableView来实现。 在Flutter中,也有对应的列表Widget,就是ListView。

2.1 ListView基础

2.1.1 ListView基本使用

ListView可以沿一个方向【垂直或水平方向,默认是垂直方向】来排列其所有子Widget。一种最简单的使用方法是直接将所有需要排列的子Widgett放在ListView的children属性中即可。

ListView代码演练

为了让文字之间有一些间距,使用了Padding Widget

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ZXYHomePage(),
    );
  }
}

class ZXYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Flutter滚动Widget"),
      ),
      body: ZXYHomeBody(),
    );
  }
}

class ZXYHomeBody extends StatelessWidget {

  final TextStyle textStyle = TextStyle(fontSize: 20, color: Colors.redAccent);

  @override
  Widget build(BuildContext context) {
    return ListView(
      children: <Widget>[
        Padding(
            padding: const EdgeInsets.all(9.0),
            child: Text("人的一切痛苦,本质上都是对自己无能的愤怒。", style: textStyle),
        ),
        Padding(
          padding: const EdgeInsets.all(9.0),
          child: Text("人活在世界上,不可以有偏差;而且多少要费点劲儿,才能把自己保持到理性的轨道上。", style: textStyle),
        ),
        Padding(
          padding: const EdgeInsets.all(9.0),
          child: Text("我活在世上,无非想要明白些道理,遇见些有趣的事。", style: textStyle),
        ),

      ],
    );
  }
}

运行结果

2.1.2 ListTitle的使用

在开发中,经常见到一种列表,有一个图标或图片(Icon),有一个标题(Title),有一个子标题(Subtitle),还有尾部一个图标(Icon)。

这个时候,可以使用ListTile来实现。

代码演练:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ZXYHomePage(),
    );
  }
}

class ZXYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Flutter滚动Widget"),
      ),
      body: ZXYHomeBody(),
    );
  }
}

class ZXYHomeBody extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return ListView(
      children: <Widget>[
        ListTile(
          leading: Icon(Icons.people, size: 36,),
          title: Text("联系人"),
          subtitle: Text("联系人信息"),
          trailing: Icon(Icons.arrow_forward_ios),
        ),
        ListTile(
          leading: Icon(Icons.email, size: 36,),
          title: Text("邮箱"),
          subtitle: Text("邮箱地址信息"),
          trailing: Icon(Icons.arrow_forward_ios),
        ),
        ListTile(
          leading: Icon(Icons.message, size: 36,),
          title: Text("消息"),
          subtitle: Text("消息详情信息"),
          trailing: Icon(Icons.arrow_forward_ios),
        ),
        ListTile(
          leading: Icon(Icons.map, size: 36,),
          title: Text("地址"),
          subtitle: Text("地址详情信息"),
          trailing: Icon(Icons.arrow_forward_ios),
        )
      ],
    );
  }
}

运行结果:

2.1.3 垂直方向滚动

可以通过设置 scrollDirection 参数来控制视图的滚动方向。

通过下面的代码实现一个水平滚动的内容:

  • 这里需要注意,需要给Container设置width,否则它是没有宽度的,就不能正常显示。

  • 或者我们也可以给ListView设置一个itemExtent,该属性会设置滚动方向上每个item所占据的宽度。

代码演练

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ZXYHomePage(),
    );
  }
}

class ZXYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Flutter滚动Widget"),
      ),
      body: ZXYHomeBody(),
    );
  }
}

class ZXYHomeBody extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return ListView(
      scrollDirection: Axis.horizontal,
      itemExtent: 200,
      children: <Widget>[
        Container(color: Colors.redAccent,width: 200,),
        Container(color: Colors.blue,width: 200,),
        Container(color: Colors.yellow,width: 200,),
        Container(color: Colors.purple,width: 200,),
        Container(color: Colors.pink,width: 200,),
        Container(color: Colors.orange,width: 200,),
      ],
    );
  }
}

运行结果:

2.2 ListView.build

通过构造函数中的children传入所有的子Widget有一个问题:默认会创建出所有的子Widget。 但是对于用户来说,一次性构建出所有的Widget并不会有什么差异,但是对于我们的程序来说会产生性能问题,而且会增加首屏的渲染时间。 可以ListView.build来构建子Widget,提供性能。

2.2.1 ListView.build基本使用

ListView.build适用于子Widget比较多的场景,该构造函数将创建子Widget交给了一个抽象的方法,交给ListView进行管理,ListView会在真正需要的时候去创建子Widget,而不是一开始就全部初始化好。

该方法有两个重要参数:

  • itemBuilder:列表项创建的方法。当列表滚动到对应位置的时候,ListView会自动调用该方法来创建对应的子Widget。类型是IndexedWidgetBuilder,是一个函数类型。 

  • itemCount:表示列表项的数量,如果为空,则表示ListView为无限列表。

代码演练1:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ZXYHomePage(),
    );
  }
}

class ZXYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Flutter滚动Widget"),
      ),
      body: ZXYHomeBody(),
    );
  }
}

class ZXYHomeBody extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: 100,
      itemExtent: 80,
      itemBuilder: (BuildContext context, int index){
        return ListTile(title: Text("标题$index"), subtitle: Text("详情内容$index"));
      },
    );
  }
}

运行结果1:

代码演练2:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ZXYHomePage(),
    );
  }
}

class ZXYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Flutter滚动Widget"),
      ),
      body: ZXYHomeBody(),
    );
  }
}

class ZXYHomeBody extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return ListView(
      children: List.generate(100, (index) {
        return ListTile(
          leading: Icon(Icons.people),
          trailing: Icon(Icons.delete),
          title: Text("联系人${index + 1}"),
          subtitle: Text("联系人电话号码:18866665555"),
        );
      }),
    );
  }
}

运行结果2:

2.2.2 ListView.separated

ListView.separated可以生成列表项之间的分割器,它除了比ListView.builder多了一个separatorBuilder参数,该参数是一个分割器生成器。

需求:奇数行添加一条蓝色下划线,偶数行添加一条红色下划线:

代码演练:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ZXYHomePage(),
    );
  }
}

class ZXYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Flutter滚动Widget"),
      ),
      body: ZXYHomeBody(),
    );
  }
}

class ZXYHomeBody extends StatelessWidget {

  Divider blueColor = Divider(color: Colors.blue,);
  Divider redColor = Divider(color: Colors.red,);

  @override
  Widget build(BuildContext context) {
      return ListView.separated(
          itemBuilder: (BuildContext context, int index){
            return ListTile(
              leading: Icon(Icons.people),
              title: Text("联系人${index+1}"),
              subtitle: Text("联系人电话${index+1}"),
            );
          },
          separatorBuilder: (BuildContext context, int index){
            return index % 2 == 0 ? redColor : blueColor;
          },
          itemCount: 100
      );
  }
}

运行结果:

GridView组件

GridView用于展示多列的展示,在开发中也非常常见,比如直播App中的主播列表、电商中的商品列表等等。 在Flutter中可以使用GridView来实现,使用方式和ListView也比较相似。

3.1 GridView构造函数

学习GridView构造函数的使用方法

一种使用GridView的方式就是使用构造函数来创建,和ListView对比有一个特殊的参数:gridDelegate,``gridDelegate用于控制交叉轴的item数量或者宽度,需要传入的类型是SliverGridDelegate,但是它是一个抽象类,所以需要传入它的子类:

1、SliverGridDelegateWithFixedCrossAxisCount

SliverGridDelegateWithFixedCrossAxisCount({
  @required double crossAxisCount, // 交叉轴的item个数
  double mainAxisSpacing = 0.0, // 主轴的间距
  double crossAxisSpacing = 0.0, // 交叉轴的间距
  double childAspectRatio = 1.0, // 子Widget的宽高比
})

代码演练1:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ZXYHomePage(),
    );
  }
}

class ZXYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Flutter滚动Widget"),
      ),
      body: ZXYHomeBody(),
    );
  }
}

class ZXYHomeBody extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
      return GridView(
          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 3,
            mainAxisSpacing: 10,
            crossAxisSpacing: 10,
            childAspectRatio: 1.0
          ),
        children: getGridWidgets(),
      );
  }

  List<Widget> getGridWidgets() {
    return List.generate(100, (index){
      return Container(
        color: Colors.purple,
        alignment: Alignment(0,0),
        child: Text("item$index", style: TextStyle(fontSize: 20, color: Colors.white)),
      );
    });
  }
}

运行结果:

2、SliverGridDelegateWithMaxCrossAxisExtent

SliverGridDelegateWithMaxCrossAxisExtent({
  double maxCrossAxisExtent, // 交叉轴的item宽度
  double mainAxisSpacing = 0.0, // 主轴的间距
  double crossAxisSpacing = 0.0, // 交叉轴的间距
  double childAspectRatio = 1.0, // 子Widget的宽高比
})

代码演练:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ZXYHomePage(),
    );
  }
}

class ZXYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("GridView滚动"),
      ),
      body: ZXYHomeBody(),
    );
  }
}

class ZXYHomeBody extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return GridView(
      gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
          maxCrossAxisExtent: 150,
          mainAxisSpacing: 10,
          crossAxisSpacing: 10,
          childAspectRatio: 1.0
      ),
      children: getGridWidgets(),
    );
  }

  List<Widget> getGridWidgets() {
    return List.generate(100, (index){
      return Container(
        color: Colors.purple,
        alignment: Alignment(0,0),
        child: Text("item$index", style: TextStyle(fontSize: 20, color: Colors.white)),
      );
    });
  }
}

运行结果:

前面两种方式也可以不设置delegate,直接使用**GridView.count构造函数****GridView.extent**构造函数实现相同的效果

3.2 GridView.build

和ListView一样,使用构造函数会一次性创建所有的子Widget,会带来性能问题,所以可以使用GridView.build来交给GridView自己管理需要创建的子Widget。

在之前,搞了yz.json数据,现在动态的来通过JSON数据展示一个列表。

思考:这个时候是否依然可以使用StatelessWidget:

答案:不可以,因为当前我们的数据是异步加载的,刚开始界面并不会展示数据(没有数据),后面从JSON中加载出来数据(有数据)后,再次展示加载的数据。

  • 这里是有状态的变化的,从无数据,到有数据的变化。

  • 这个时候,我们需要使用StatefulWidget来管理组件。

【后面讲述 网络请求时-下一篇】

Slivers

考虑一个这样的布局:一个滑动的视图中包括一个标题视图(HeaderView),一个列表视图(ListView),一个网格视图(GridView)。 怎么可以让它们做到统一的滑动效果呢?使用前面的滚动是很难做到的。 Flutter中有一个可以完成这样滚动效果的Widget:CustomScrollView,可以统一管理多个滚动视图。 在CustomScrollView中,每一个独立的,可滚动的Widget被称之为Sliver。 补充:Sliver可以翻译成裂片、薄片,你可以将每一个独立的滚动视图当做一个小裂片。

4.1 Slivers的基本使用

因为需要把很多的Sliver放在一个CustomScrollView中,所以CustomScrollView有一个slivers属性,里面让放对应的一些Sliver:

  • SliverList:类似于之前使用过的ListView;

  • SliverFixedExtentList:类似于SliverList只是可以设置滚动的高度;

  • SliverGrid:类似于之前使用过的GridView;

  • SliverPadding:设置Sliver的内边距,因为可能要单独给Sliver设置内边距;

  • SliverAppBar:添加一个AppBar,通常用来作为CustomScrollView的HeaderView;

  • SliverSafeArea:设置内容显示在安全区域(比如不让留海挡住的内容)

代码演练:SliverGrid+SliverPadding+SliverSafeArea的组合

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ZXYHomePage(),
    );
  }
}

class ZXYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Sliver问题"),
      ),
      body: ZXYHomeBody(),
    );
  }
}

class ZXYHomeBody extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: <Widget>[
        SliverSafeArea(
          sliver: SliverPadding(
            padding: EdgeInsets.all(8),
            sliver: SliverGrid(
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 2,
                crossAxisSpacing: 8,
                mainAxisSpacing: 8
              ),
              delegate: SliverChildBuilderDelegate(
                  (BuildContext context, int index) {
                    return Container(
                      alignment: Alignment(0, 0),
                      color: Colors.orange,
                      child: Text("item$index"),
                    );
                  },
                childCount: 20
              ),
            ),
          )
        ),

      ],
    );
  }

}

运行结果:

4.2 Slivers的组合使用

这里使用官方的示例程序,将SliverAppBar+SliverGrid+SliverFixedExtentList

演练代码:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ZXYHomePage(),
    );
  }
}

class ZXYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Sliver组合使用"),
      ),
      body: ZXYHomeBody(),
    );
  }
}

class ZXYHomeBody extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return showCustomScrollView();
  }

  Widget showCustomScrollView() {
    return new CustomScrollView(
      slivers: <Widget>[
        const SliverAppBar(
          expandedHeight: 250.0,
          flexibleSpace: FlexibleSpaceBar(
            title: Text('ZXY列表Demo'),
            background: Image(
              image: NetworkImage(
                "https://tva1.sinaimg.cn/large/006y8mN6gy1g72j6nk1d4j30u00k0n0j.jpg",
              ),
              fit: BoxFit.cover,
            ),
          ),
        ),
        new SliverGrid(
          gridDelegate: new SliverGridDelegateWithMaxCrossAxisExtent(
            maxCrossAxisExtent: 200.0,
            mainAxisSpacing: 10.0,
            crossAxisSpacing: 10.0,
            childAspectRatio: 4.0,
          ),
          delegate: new SliverChildBuilderDelegate(
                (BuildContext context, int index) {
              return new Container(
                alignment: Alignment.center,
                color: Colors.teal[100 * (index % 9)],
                child: new Text('grid item $index'),
              );
            },
            childCount: 10,
          ),
        ),
        SliverFixedExtentList(
          itemExtent: 50.0,
          delegate: SliverChildBuilderDelegate(
                  (BuildContext context, int index) {
                return new Container(
                  alignment: Alignment.center,
                  color: Colors.lightBlue[100 * (index % 9)],
                  child: new Text('list item $index'),
                );
              },
              childCount: 20
          ),
        ),
      ],
    );
  }
}

运行结果:

监听滚动事件

对于滚动的视图,经常需要监听一些滚动事件,在监听到的时候去做对应的一些事情。
比如视图滚动到底部时,可能希望做上拉加载更多;
比如滚动到一定位置时显示一个回到顶部的按钮,点击回到顶部的按钮,回到顶部;
比如监听滚动什么时候开始,什么时候结束;
在Flutter中监听滚动相关的内容由两部分组成:ScrollController和ScrollNotification

5.1 ScrollController

在Flutter中,Widget并不是最终渲染到屏幕上的元素(真正渲染的是RenderObject),因此通常这种监听事件以及相关的信息并不能直接从Widget中获取,而是必须通过对应的Widget的Controller来实现。

ListView、GridView的组件控制器是ScrollController,可以通过它来获取视图的滚动信息,并且可以调用里面的方法来更新视图的滚动位置。

另外,通常情况下,会根据滚动的位置来改变一些Widget的状态信息,所以ScrollController通常会和StatefulWidget一起来使用,并且会在其中控制它的初始化、监听、销毁等事件。

需求:来做一个案例,当滚动到1000位置的时候,显示一个回到顶部的按钮:

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

  • ScrollController间接继承自Listenable,可以根据ScrollController来监听滚动事件。

代码演练:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ZXYHomePage(),
    );
  }
}

class ZXYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ZXYHomeBody();
  }
}

class ZXYHomeBody extends StatefulWidget {

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

class _ZXYHomeBodyState extends State<ZXYHomeBody> {

  bool _isShowTop = false;
  ScrollController _controller;

  @override
  void initState() {
    //初始化ScrollController
    _controller = ScrollController();

    //监听滚动
    _controller.addListener(() {
      var tempShowTop = _controller.offset >= 1000;
      if (tempShowTop != _isShowTop) {
        setState(() {
          _isShowTop = tempShowTop;
        });
      }
    });

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("ListView展示"),
      ),
      body: ListView.builder(
          itemCount: 100,
          itemExtent: 60,
          controller: _controller,
          itemBuilder: (BuildContext context, int index) {
            return ListTile(title: Text("item$index"));
          }
      ),
      floatingActionButton: !_isShowTop ? null : FloatingActionButton(
        child: Icon(Icons.arrow_upward),
        onPressed: () {
          _controller.animateTo(0, duration: Duration(milliseconds: 1000), curve: Curves.ease);
        },
      ),
    );
  }
}

运行结果

5.2 NotificationListener

如果希望监听什么时候开始滚动,什么时候结束滚动,这个时候我们可以通过NotificationListener

基本知识

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

  • NotificationListener需要一个onNotification回调函数,用于实现监听处理逻辑。

  • 该回调可以返回一个布尔值,代表是否阻止该事件继续向上冒泡,如果为true时,则冒泡终止,事件停止向上传播,如果不返回或者返回值为false 时,则冒泡继续

**需求:**列表滚动,并且在中间显示滚动进度

代码演练:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ZXYHomePage(),
    );
  }
}

class ZXYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("监听滚动"),
      ),
      body: ZXYHomeBody(),
    );
  }
}

class ZXYHomeBody extends StatefulWidget {

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

class _ZXYHomeBodyState extends State<ZXYHomeBody> {

  int _progress = 0;

  @override
  Widget build(BuildContext context) {
    return NotificationListener(
      onNotification: (ScrollNotification notification) {
        // 1.判断监听事件的类型
        if (notification is ScrollStartNotification) {
          print("开始滚动.....");
        } else if (notification is ScrollUpdateNotification) {
          // 当前滚动的位置和总长度
          final currentPixel = notification.metrics.pixels;
          final totalPixel = notification.metrics.maxScrollExtent;
          double progress = currentPixel / totalPixel;
          setState(() {
            _progress = (progress * 100).toInt();
          });
          print("正在滚动:${notification.metrics.pixels} - ${notification.metrics.maxScrollExtent}");
        } else if (notification is ScrollEndNotification) {
          print("结束滚动....");
        }
        return false;
      },
      child: Stack(
        alignment: Alignment(.9, .9),
        children: <Widget>[
          ListView.builder(
              itemCount: 100,
              itemExtent: 60,
              itemBuilder: (BuildContext context, int index) {
                return ListTile(title: Text("item$index"));
              }
          ),
          CircleAvatar(
            radius: 30,
            child: Text("$_progress%"),
            backgroundColor: Colors.black54,
          )
        ],
      ),
    );
  }
}

运行结果:

机会❤️❤️❤️🌹🌹🌹

如果想和我一起共建抖音,成为一名bytedancer,Come on。期待你的加入!!!

截屏2022-06-08 下午6.09.11.png

总结

今天这篇文章,详细介绍了Flutter项目中很重要的功能实现--滚动的实现的几种方式以及监听滚动。通过上面内容的讲解,大家可以写出列表展示页以及复杂的页面组合实现。

大家可以手动的编写上面的Demo例子, 相信每个星期1-2篇相关博客,可以加深大家对Flutter项目的认知和感触【动手写!动手写!动手写!!!

下一篇博客将讲述Flutter的异步实现方式和网络请求,最后开始做项目!!!感谢大家的点赞作品及关注本人,共同进步,共勉!!!