美文网首页
Flutter入门09 -- Widget之滚动组件类

Flutter入门09 -- Widget之滚动组件类

作者: YanZi_33 | 来源:发表于2021-11-08 09:40 被阅读0次
    • 常见的滚动组件有:ListView、GridView、SingleChildScrollView、CustomScrollView、NestedScrollView、Scrollable、Scrollbar、NotificationListener、ScrollConfiguration、RefreshIndicator、PageView

    ListView

    • ListView创建的方式通常有三种,分别为ListView()ListView.builder()ListView.separated()
    ListView创建方式之ListView()
    • 构造方法如下:
    ListView({
        Key key,
        Axis scrollDirection = Axis.vertical,
        bool reverse = false,
        ScrollController controller,
        bool primary,
        ScrollPhysics physics,
        bool shrinkWrap = false,
        EdgeInsetsGeometry padding,
        this.itemExtent,
        bool addAutomaticKeepAlives = true,
        bool addRepaintBoundaries = true,
        bool addSemanticIndexes = true,
        double cacheExtent,
        List<Widget> children = const <Widget>[],
        int semanticChildCount,
        DragStartBehavior dragStartBehavior = DragStartBehavior.start,
        ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
    })
    
    • scrollDirection:滚动方向,属于Axis类型;
    • itemExtent:item单元格的高度;
    • children:单元格数组,这里的单元格指的是ListTile;
    • shrinkWrap:其值为true时,会将item子组件的高度相加,然后赋值给ListView的高度,容易引发ListView高度溢出异常的问题;
    • 案例代码如下:
    class SFHomeContent extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return ListView(
          scrollDirection: Axis.vertical,
          itemExtent: 100,//设置Item的高度
          children: List.generate(100, (index) {
            return ListTile(
              leading: Icon(Icons.people),
              trailing: Icon(Icons.delete),
              title: Text("联系人${index+1}"),
              subtitle: Text("联系人电话号码:19991604555"),
            );
          }),
        );
      }
    }
    
    • 效果图如下:
    image.png
    • 通过ListView()创建,会一次性创建100个Item,这样性能比较差,其适用于Item个数确定,且数量较少的情况下才会采用;
    ListView创建方式之ListView.builder()
    • 不会一次性创建所有Item,而是需要展示的Item才会去创建,性能较好,构造方法如下:
    ListView.builder({
        Key key,
        Axis scrollDirection = Axis.vertical,
        bool reverse = false,
        ScrollController controller,
        bool primary,
        ScrollPhysics physics,
        bool shrinkWrap = false,
        EdgeInsetsGeometry padding,
        this.itemExtent,
        @required IndexedWidgetBuilder itemBuilder,
        int itemCount,
        bool addAutomaticKeepAlives = true,
        bool addRepaintBoundaries = true,
        bool addSemanticIndexes = true,
        double cacheExtent,
        int semanticChildCount,
        DragStartBehavior dragStartBehavior = DragStartBehavior.start,
        ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
    })
    
    • scrollDirection:滚动方向,属于Axis类型;
    • itemExtent:单元格高度;
    • itemCount:单元格数量;
    • itemBuilder:获取item单元格,其类型为Widget Function(BuildContext context, int index),调用函数创建item;
    • 案例代码如下:
    class SFHomeBody extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // TODO: implement build
        return ListView.builder(
          itemExtent: 80,
          itemCount: 100,
          itemBuilder: (BuildContext context,int index) {
            return Container(
                color: Colors.red,
                child: Text("Hello World!!! ${index}",style: TextStyle(fontSize: 20)),
                padding: EdgeInsets.all(10),
            );
          }
        );
      }
    }
    
    • 效果图如下:
    Snip20220214_22.png
    ListView创建方式之ListView.separated()
    • 创建带有分割线的ListView,构造函数如下:
    ListView.separated({
        Key key,
        Axis scrollDirection = Axis.vertical,
        bool reverse = false,
        ScrollController controller,
        bool primary,
        ScrollPhysics physics,
        bool shrinkWrap = false,
        EdgeInsetsGeometry padding,
        @required IndexedWidgetBuilder itemBuilder,
        @required IndexedWidgetBuilder separatorBuilder,
        @required int itemCount,
        bool addAutomaticKeepAlives = true,
        bool addRepaintBoundaries = true,
        bool addSemanticIndexes = true,
        double cacheExtent,
        DragStartBehavior dragStartBehavior = DragStartBehavior.start,
        ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
    })
    
    • scrollDirection:滚动方向,属于Axis类型;
    • itemExtent:单元格高度;
    • itemCount:单元格数量;
    • itemBuilder:获取item单元格组件,其类型为Widget Function(BuildContext context, int index),调用函数创建item;
    • separatorBuilder:获取分割线组件,类型同上;
    • 不能设置单元格item的高度
    • 案例代码如下:
    class SFHomeContent extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return ListView.separated(
            itemBuilder: (BuildContext ctx, int index){
              return Text("Hello World!!! ${index}",style: TextStyle(fontSize: 20),);
            },
            //分割线
            separatorBuilder: (BuildContext ctx,int index){
              return Divider(color: Colors.red,indent: 20,endIndent: 20,thickness: 5);
            },
            itemCount: 100
        );
      }
    }
    
    • 效果图如下:
    image.png

    GridView

    • GridView的创建方式有:GridView()GridView.builder()
    创建方式之GridView()
    • 构造方法如下所示:
    GridView({
        Key key,
        Axis scrollDirection = Axis.vertical,
        bool reverse = false,
        ScrollController controller,
        bool primary,
        ScrollPhysics physics,
        bool shrinkWrap = false,
        EdgeInsetsGeometry padding,
        @required this.gridDelegate,
        bool addAutomaticKeepAlives = true,
        bool addRepaintBoundaries = true,
        bool addSemanticIndexes = true,
        double cacheExtent,
        List<Widget> children = const <Widget>[],
        int semanticChildCount,
        DragStartBehavior dragStartBehavior = DragStartBehavior.start,
        ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
    }) 
    
    • children:子组件数组,item单元格数组;
    • scrollDirection:表示滚动方向,默认为垂直方向,属于Axis类型;
    • gridDelegate:GridView的代理,属于SliverGridDelegate类型,其本质是控制GridView如何排列内部子元素的一个委托;
      • SliverGridDelegate是一个抽象类,其有两个实现子类,如下所示:
      • SliverGridDelegateWithFixedCrossAxisCount:用于固定列数的场景;
      • SliverGridDelegateWithMaxCrossAxisExtent:用于子元素有最大宽度限制的场景;
    • controller:GridView的滚动监听者;
    • primary:当内容不足以滚动时,是否支持滚动;
    • 使用gridDelegateSliverGridDelegateWithFixedCrossAxisCount来创建GridView,其中SliverGridDelegateWithFixedCrossAxisCount的构造方法如下:
    const SliverGridDelegateWithFixedCrossAxisCount({
        @required this.crossAxisCount,
        this.mainAxisSpacing = 0.0,
        this.crossAxisSpacing = 0.0,
        this.childAspectRatio = 1.0,
    })
    
    • 交叉轴方向上item数量固定,其宽度根据屏幕的宽度与item的数量进行计算;
    • crossAxisCount:交叉轴方向(水平方向)item的个数;
    • childAspectRatio:item的宽高比;
    • crossAxisSpacing:交叉轴方向上 item之间的间距;
    • mainAxisSpacing:主轴方向上 item之间的间距;
    • 案例代码如下:
    class SFHomeContent extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return GridView(
          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 3,
            childAspectRatio: 1.5,
            crossAxisSpacing: 8,
            mainAxisSpacing: 8
          ),
          children:
            List.generate(100, (index) {
               return Container(
                 color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256)),
               );
            }),
        );
      }
    }
    
    • 效果图如下所示:
    image.png
    • 使用gridDelegateSliverGridDelegateWithMaxCrossAxisExtent来创建GridView,其中SliverGridDelegateWithMaxCrossAxisExtent的构造方法如下:
    const SliverGridDelegateWithMaxCrossAxisExtent({
        @required this.maxCrossAxisExtent,
        this.mainAxisSpacing = 0.0,
        this.crossAxisSpacing = 0.0,
        this.childAspectRatio = 1.0,
    })
    
    • 交叉轴方向上的设置item的最大宽度,item的个数不固定;
    • maxCrossAxisExtent:交叉轴方向上item的最大宽度;
    • childAspectRatio:item的宽高比;
    • crossAxisSpacing:交叉轴方向上 item之间的间距;
    • mainAxisSpacing:主轴方向上 item之间的间距;
    • 案例代码如下:
    class SFHomeContent extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return GridView(
          gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
            maxCrossAxisExtent: 220,
            crossAxisSpacing: 8,
            mainAxisSpacing: 8,
            childAspectRatio: 1.5
          ),
          children: List.generate(100, (index) {
            return Container(
              color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256),Random().nextInt(256)),
            );
          })
        );
      }
    }
    
    • 效果图如下:
    image.png
    创建方式之GridView.builder()
    • 构造方法如下:
      GridView.builder({
        Key key,
        Axis scrollDirection = Axis.vertical,
        bool reverse = false,
        ScrollController controller,
        bool primary,
        ScrollPhysics physics,
        bool shrinkWrap = false,
        EdgeInsetsGeometry padding,
        @required this.gridDelegate,
        @required IndexedWidgetBuilder itemBuilder,
        int itemCount,
        bool addAutomaticKeepAlives = true,
        bool addRepaintBoundaries = true,
        bool addSemanticIndexes = true,
        double cacheExtent,
        int semanticChildCount,
        DragStartBehavior dragStartBehavior = DragStartBehavior.start,
        ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
    })
    
    • 属性大部分相同;
    • gridDelegate:GridView的代理,属于SliverGridDelegate类型,其本质是控制GridView如何排列内部子元素的一个委托;
    • itemBuilder:创建item的构造函数;
    • itemCount:item的数量;
    • 案例代码如下:
    class SFHomeBody extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // TODO: implement build
        return GridView.builder(
            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 3,
              mainAxisSpacing: 10,
              crossAxisSpacing: 10,
              childAspectRatio: 1.5
            ),
            itemBuilder: (BuildContext ctx,int index){
              return Container(color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256),Random().nextInt(256)));
            },
          itemCount: 100,
        );
      }
    }
    
    • 效果图如下:
    image.png

    CustomScrollView

    • CustomScrollView:自定义滚动组件,需要传入Slivers即Sliver数组,我们知道ListViewGridView都是继承自BoxScrollView,而BoxScrollView是一个抽象类,从源码来看ListViewGridView在创建的过程中都需要执行buildSlivers方法,其内部调用buildChildLayout方法,这是一个抽象方法,分别由ListViewGridView来实现,最终提供一个Sliver数组,其中ListView提供的Sliver为SliverFixedExtentListGridView提供的Sliver为SliverGrid
    • CustomScrollView的构造函数如下:
    const CustomScrollView({
        Key key,
        Axis scrollDirection = Axis.vertical,
        bool reverse = false,
        ScrollController controller,
        bool primary,
        ScrollPhysics physics,
        bool shrinkWrap = false,
        Key center,
        double anchor = 0.0,
        double cacheExtent,
        this.slivers = const <Widget>[],
        int semanticChildCount,
        DragStartBehavior dragStartBehavior = DragStartBehavior.start,
    })
    
    • slivers:Sliver的数组,最重要的一个参数;
    • 常见的Sliver组件如下:
      • SliverSafeArea
      • SliverAppBar
      • SliverList
      • SliverGrid
      • SliverPadding
      • SliverFixedExtentList
      • SliverPrototypeExtentList
      • SliverToBoxAdapter
      • SliverPersistentHeader
    单个Sliver -- SliverSafeArea
    • SliverSafeArea的构造函数如下:
    const SliverSafeArea({
        Key key,
        this.left = true,
        this.top = true,
        this.right = true,
        this.bottom = true,
        this.minimum = EdgeInsets.zero,
        @required this.sliver,
    }) 
    
    • 案例代码如下:
    import 'dart:math';
    import 'package:flutter/material.dart';
    
    void main() => runApp(SFMyApp());
    
    class SFMyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(home: SFHomePage());
      }
    }
    
    class SFHomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(title: Text("基础widget")),
            body: SFHomeContent());
      }
    }
    
    class SFHomeContent extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return SliverDemo1();
      }
    }
    
    class SliverDemo1 extends StatelessWidget {
      const SliverDemo1({
        Key key,
      }) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return CustomScrollView(
          slivers: [
            SliverSafeArea(
              sliver: SliverPadding(
                padding: EdgeInsets.all(8),
                sliver: SliverGrid(
                  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: 2,
                    crossAxisSpacing: 8,
                    mainAxisSpacing: 8,
                    childAspectRatio: 1.5
                  ),
                  delegate: SliverChildBuilderDelegate(
                      (BuildContext ctx,int index){
                        return Container(
                          color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256),Random().nextInt(256)),
                        );
                      },
                    childCount: 100
                  ),
                ),
              ),
            )
          ],
        );
      }
    }
    
    • 自定义CustomScrollView,需传入Slivers数组,这里传入的Sliver为SliverSafeArea
    • SliverPadding:属于Sliver组件,可设置内边距;
    • SliverGrid:属于Sliver组件,是实现网格视图的核心组件,其构造函数如下:
    const SliverGrid({
        Key key,
        @required SliverChildDelegate delegate,
        @required this.gridDelegate,
     })
    
    • gridDelegate:GridView的代理,属于SliverGridDelegate类型,其本质是控制GridView如何排列内部子元素的一个委托;
    • delegate:是提供item组件,类型为SliverChildDelegate
    • SliverChildDelegate是抽象类,其作用是用来创建滚动组件的item,其有两个子类分别为SliverChildListDelegateSliverChildBuilderDelegate
      • SliverChildListDelegate:性能较差,item一次性创建所有;
      • SliverChildBuilderDelegate:性能较好,创建需要展示的item,其构造函数如下:
    const SliverChildBuilderDelegate(
        this.builder, {
        this.findChildIndexCallback,
        this.childCount,
        this.addAutomaticKeepAlives = true,
        this.addRepaintBoundaries = true,
        this.addSemanticIndexes = true,
        this.semanticIndexCallback = _kDefaultSemanticIndexCallback,
        this.semanticIndexOffset = 0,
    }) 
    
    • builder:是一个item的构造函数;

    • childCount:item的数量;

    • SafeAreaSliverSafeArea的区别:

      • SafeArea:安全区域,让目标组件在安全区域内显示;
      • SliverSafeArea:给Sliver设置安全区域,且在滚动时可以在非安全区域内滚动,而SafeArea不可以;
    • 效果图如下:

    image.png
    多个Sliver (SliverAppBar + SliverGrid + SliverList)
    import 'dart:math';
    import 'package:flutter/material.dart';
    
    void main() => runApp(SFMyApp());
    
    class SFMyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(home: SFHomePage());
      }
    }
    
    class SFHomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            // appBar: AppBar(title: Text("基础widget")),
            body: SFHomeContent());
      }
    }
    
    class SFHomeContent extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return CustomScrollView(
          slivers: [
            SliverAppBar(
              pinned: true,
              expandedHeight: 200,
              flexibleSpace: FlexibleSpaceBar(
                title: Text("Hello World!!",style: TextStyle(fontSize: 25),),
              ),
            ),
            SliverGrid(
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 2,
                  crossAxisSpacing: 8,
                  mainAxisSpacing: 8,
                  childAspectRatio: 2.5
              ),
                delegate: SliverChildBuilderDelegate(
                  (BuildContext ctx,int index){
                      return Container(
                        color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256),Random().nextInt(256)),
                      );
                   },
                 childCount: 10
                ),
            ),
            SliverList(
              delegate: SliverChildBuilderDelegate(
                  (BuildContext ctx,int index){
                    return ListTile(
                      leading: Icon(Icons.people),
                      title: Text("联系人$index"),
                    );
                  },
                childCount: 20
              ),
            )
          ],
        );
      }
    }
    
    • slivers数组中传入了SliverAppBarSliverGridSliverList三种类型的Sliver,效果如下:
      image.png

    滚动组件的监听

    • 滚动组件的监听通常有两种方式,分别为controllerNotificationListener
    controller监听
    • 可以设置默认值offset;
    • 监听滚动,也可以监听滚动的位置;
    • 案例代码:
    import 'package:flutter/material.dart';
    
    void main() => runApp(SFMyApp());
    
    class SFMyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(home: SFHomePage());
      }
    }
    
    class SFHomePage extends StatefulWidget {
      @override
      _SFHomePageState createState() => _SFHomePageState();
    }
    
    class _SFHomePageState extends State<SFHomePage> {
      ScrollController controller = ScrollController(initialScrollOffset: 300);
      bool isShowFloatButton = false;
    
      @override
      void initState() {
        super.initState();
        controller.addListener(() {
          print("监听到滚动: ${controller.offset}");
          setState(() {
            isShowFloatButton = controller.offset >= 1000;
          });
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text("基础widget")),
          body: SFHomeContent(controller),
          floatingActionButton: isShowFloatButton ? FloatingActionButton(
            child: Icon(Icons.arrow_upward),
            onPressed: (){
              controller.animateTo(0, duration: Duration(seconds: 1), curve: Curves.easeIn);
            },
          ) : null,
        );
      }
    }
    
    class SFHomeContent extends StatelessWidget {
      final ScrollController controller;
      SFHomeContent(this.controller);
    
      @override
      Widget build(BuildContext context) {
        return ListView.builder(
          controller: controller,
          itemBuilder: (BuildContext ctx, int index) {
            return ListTile(
              leading: Icon(Icons.people),
              title: Text("联系人$index"),
            );
          },
          itemCount: 100,
        );
      }
    }
    
    • 右下角悬浮按钮,当前滚动偏移量>=1000时显示;
    NotificationListener监听
    • 案例代码:
    import 'package:flutter/material.dart';
    
    void main() => runApp(SFMyApp());
    
    class SFMyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(home: SFHomePage());
      }
    }
    
    class SFHomePage extends StatefulWidget {
      @override
      _SFHomePageState createState() => _SFHomePageState();
    }
    
    class _SFHomePageState extends State<SFHomePage> {
      bool isShowFloatButton = false;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text("基础widget")),
          body: SFHomeContent(),
          floatingActionButton: isShowFloatButton
              ? FloatingActionButton(
                  child: Icon(Icons.arrow_upward),
                  onPressed: () {
    
                  },
                )
              : null,
        );
      }
    }
    
    class SFHomeContent extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return NotificationListener(
          onNotification: (ScrollNotification notification){
            if(notification is ScrollStartNotification){
              print("开始滚动");
            }else if (notification is ScrollUpdateNotification){
              print("正在滚动 -- 总区域:${notification.metrics.maxScrollExtent} 当前位置: ${notification.metrics.pixels}");
            }else if (notification is ScrollEndNotification){
              print("结束滚动");
    
            }
    
            return true;
          },
          child: ListView.builder(
            itemBuilder: (BuildContext ctx, int index) {
              return ListTile(
                leading: Icon(Icons.people),
                title: Text("联系人$index"),
              );
            },
            itemCount: 100,
          ),
        );
      }
    }
    

    相关文章

      网友评论

          本文标题:Flutter入门09 -- Widget之滚动组件类

          本文链接:https://www.haomeiwen.com/subject/lmctaltx.html