Flutter开发实战初级:ListView详解(2)

  • 时间:2022-03-15 14:17 作者:iOS沐橙君 来源: 阅读:543
  • 扫一扫,手机访问
摘要:ListView 知识点在Flutter中,用ListView来显示列表项,支持垂直和水平方向展现,通过一个属性我们即可以控制其方向1.水平的列表2.垂直的列表3.数据量非常大的列表4.内置的ListTile(挺好用的)ListView Demodemo 下载地址:flutter_listviewd

ListView 知识点

在Flutter中,用ListView来显示列表项,支持垂直和水平方向展现,通过一个属性我们即可以控制其方向
1.水平的列表
2.垂直的列表
3.数据量非常大的列表
4.内置的ListTile(挺好用的)

ListView Demo

  1. demo 下载地址:flutter_listviewdemo
  2. 运行效果:


    image.png

1. 新建car.dart 保存模型信息

  1. 定义一个Car
class Car {  const Car({    this.name,    this.imageUrl,  });  final String name;  final String imageUrl;}
  1. 定义一个数组保存Car对象
//模型数组final List<Car> datas = [  Car(    name: '保时捷918 Spyder',    imageUrl:    'https://upload-images.jianshu.io/upload_images/2990730-7d8be6ebc4c7c95b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240',  ),  Car(    name: '兰博基尼Aventador',    imageUrl:    'https://upload-images.jianshu.io/upload_images/2990730-e3bfd824f30afaac?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240',  ),  Car(    name: '法拉利Enzo',    imageUrl:    'https://upload-images.jianshu.io/upload_images/2990730-a1d64cf5da2d9d99?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240',  ),  Car(    name: 'Zenvo ST1',    imageUrl:    'https://upload-images.jianshu.io/upload_images/2990730-bf883b46690f93ce?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240',  ),  Car(    name: '迈凯伦F1',    imageUrl:    'https://upload-images.jianshu.io/upload_images/2990730-5a7b5550a19b8342?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240',  ),  Car(    name: '萨林S7',    imageUrl:    'https://upload-images.jianshu.io/upload_images/2990730-2e128d18144ad5b8?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240',  ),  Car(    name: '科尼赛克CCR',    imageUrl:    'https://upload-images.jianshu.io/upload_images/2990730-01ced8f6f95219ec?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240',  ),  Car(    name: '布加迪Chiron',    imageUrl:    'https://upload-images.jianshu.io/upload_images/2990730-7fc8359eb61adac0?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240',  ),  Car(    name: '轩尼诗Venom GT',    imageUrl:    'https://upload-images.jianshu.io/upload_images/2990730-d332bf510d61bbc2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240',  ),  Car(    name: '西贝尔Tuatara',    imageUrl:    'https://upload-images.jianshu.io/upload_images/2990730-3dd9a70b25ae6bc9?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240',  )];

2. 新建carlistview.dart 用来展现列表数据

  1. 定义Listview 展现数据
  @override  Widget build(BuildContext context) {    // TODO: implement build    return ListView.builder(     //控制方向 默认是垂直的//           scrollDirection: Axis.horizontal, //控制水平方向显示/*           children: <Widget>[              _getContainer('Maps', Icons.map),              _getContainer('phone', Icons.phone),              _getContainer('Maps', Icons.map),            ], */        itemCount: datas.length, //告诉ListView总共有多少个cell        itemBuilder: _cellForRow //使用_cellForRow回调返回每个cell        );  }
  1. 定义一个回调函数,返回每个cell
Widget _cellForRow(BuildContext context, int index) {    return Container(      color: Colors.white,      margin: EdgeInsets.all(10),      child: Column(        children: <Widget>[          Image.network(            datas[index].imageUrl          ),          SizedBox(            height: 10,          ),          Text(            datas[index].name,            style: TextStyle(              fontWeight: FontWeight.w800,              fontSize: 18.0,              fontStyle: FontStyle.values[1]            ),          ),          Container(height: 20,),        ],      ), //每人一辆跑车    );  }

3. main.dart 调用ListView

import 'package:flutter/material.dart';import 'model/carlistview.dart';//假如只有一行代码,可以是 => 代替 {}void main() => runApp(KYLApp());class KYLApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    // TODO: implement build    return MaterialApp(      debugShowCheckedModeBanner: false,      home: Home(),      theme: ThemeData(        primaryColor: Colors.yellow      ),    );  }}class Home extends StatelessWidget{  @override  Widget build(BuildContext context) {    // TODO: implement build    return Scaffold(      backgroundColor: Colors.grey[100],      appBar: AppBar(        title: Text('kongyulu first app'),      ),      body: ListViewDemo(),    );  }}

4. 知识点讲解

4.1 Widget

4.1.1 Widget基本概念
4.1.2 Widget之间的交互
4.1.3 Widget点击事件,手势

我们解决手势可以使用GestureDetector组件,它是可以增加手势的一个widget,观察它的源码:

class GestureDetector extends StatelessWidget {  GestureDetector({    Key key,    this.child,    this.onTapDown,    this.onTapUp,    this.onTap,    this.onTapCancel,    this.onDoubleTap,    this.onLongPress,    this.onLongPressUp,    this.onVerticalDragDown,    this.onVerticalDragStart,    this.onVerticalDragUpdate,    this.onVerticalDragEnd,    this.onVerticalDragCancel,    this.onHorizontalDragDown,    this.onHorizontalDragStart,    this.onHorizontalDragUpdate,    this.onHorizontalDragEnd,    this.onHorizontalDragCancel,    this.onPanDown,    this.onPanStart,    this.onPanUpdate,    this.onPanEnd,    this.onPanCancel,    this.onScaleStart,    this.onScaleUpdate,    this.onScaleEnd,    this.behavior,    this.excludeFromSemantics = false  })

可以看到GestureDetector的本质就是一个普通的widget,它拥有很多的手势onTapDown(点下),onTapUp(抬起),onTap(点击)…等,同时也拥有child属性,我们可以利用child绘制界面,利用手势解决点击事件。

4.1.4 Widget 深入探究
  1. 首先我们需要明白,Widget 是什么?这里有一个 “总所周知” 的答就是:Widget并不真正的渲染对象 。是的,事实上在 Flutter 中渲染是经历了从 Widget 到 Element 再到 RenderObject 的过程。

  2. 我们都知道 Widget 是不可变的,那么 Widget 是如何在不可变中去构建画面的?上面我们知道,Widget 是需要转化为 Element 去渲染的,而从下图注释可以看到,事实上 Widget 只是 Element 的一个配置形容 ,告诉 Element 这个实例如何去渲染。


    image.png

    那么 Widget 和 Element 之间是怎么的对应关系呢?从上图注释也可知: Widget 和 Element 之间是一对多的关系 。实际上渲染树是由 Element 实例的节点构成的树,而作为配置文件的 Widget 可能被复用到树的多个部分,对应产生多个 Element 对象。

  3. 那么RenderObject 又是什么?它和上述两个的关系是什么?从源码注释写着 An object in the render tree 可以看出到 RenderObject 才是实际的渲染对象,而通过 Element 源码我们可以看出:Element 持有 RenderObject 和 Widget。


    image.png

    再结合下图,可以大致总结出三者的关系是:配置文件 Widget 生成了 Element,然后创立 RenderObject 关联到 Element 的内部 renderObject 对象上,最后Flutter 通过 RenderObject 数据来布局和绘制。 理论上你也可以认为 RenderObject 是最终给 Flutter 的渲染数据,它保存了大小和位置等信息,Flutter 通过它去绘制出画面。


    image.png
  4. 说到 RenderObject ,就不得不说 RenderBox :A render object in a 2D Cartesian coordinate system,从源码注释可以看出,它是在继承 RenderObject 基础的布局和绘制功能上,实现了“笛卡尔坐标系”:以 Top、Left 为基点,通过宽高两个轴实现布局和嵌套的。

RenderBox 避免了直接使用 RenderObject 的麻烦场景,其中 RenderBox 的布局和计算大小是在 performLayout() 和 performResize() 这两个方法中去解决,很多时候我们更多的是选择继承 RenderBox 去实现自己设置。

  1. 综合上述情况,我们知道:
  • Widget只是显示的数据配置,所以相对而言是轻量级的存在,而 Flutter 中对 Widget 的也做了肯定的优化,所以每次改变状态导致的 Widget 重构并不会有太大的问题。
  • RenderObject 就不同了,RenderObject 涉及到布局、计算、绘制等流程,要是每次都一律重新创立开销就比较大了。
  1. 所以针对能否每次都需要创立出新的 Element 和 RenderObject 对象,Widget 都做了对应的判断以便于复用,比方:在 newWidget 与oldWidget 的 runtimeType 和 key 相等时会选择使用 newWidget 去升级已经存在的 Element 对象,不然就选择重新创立新的 Element。

由此可知:Widget 重新创立,Element 树和 RenderObject 树并不会完全重新创立。

  1. 看到这,说个题外话:那一般我们可以怎样获取布局的大小和位置呢?

首先这里需要用到我们前文中提过的 GlobalKey ,通过 key 去获取到控件对象的 BuildContext,而我们也知道 BuildContext 的实现其实是 Element,而Element持有 RenderObject 。So,我们知道的 RenderObject ,实际上获取到的就是 RenderBox ,那么通过 RenderBox 我们就只大小和位置了。

showSizes() {    RenderBox renderBoxRed = fileListKey.currentContext.findRenderObject();    print(renderBoxRed.size);  }  showPositions() {    RenderBox renderBoxRed = fileListKey.currentContext.findRenderObject();    print(renderBoxRed.localToGlobal(Offset.zero));  }

4.2 StatelessWidget和StatefulWidget

浅显点讲就是:stateful组件就是和客户交互后会有状态变化,例如滚动条Slider。stateless组件就是交互后没有状态变化,例如显示的一个文本Text。
4.2.1 基本概念和用法
  • StatefulWidget
    具备可变状态( state)的Widget(窗口小部件).
    例如系统提供的 Checkbox, Radio, Slider, InkWell, Form, and TextField 都是 stateful widgets, 他们都是 StatefulWidget的子类。
    状态( state) 是可以在构建Widget时同步读取时 和 在Widget的生命周期期间可能改变的信息

Widget实现者的责任就是 在状态改变时通过 State.setState. 立即通知状态

当您形容的客户界面部分不依赖于对象本身中的配置信息和其中构件被夸大的BuildContext时,无状态小部件很有用。对于可以动态改变的组合,例如因为具备内部时钟驱动状态,或者取决于某些系统状态,请考虑使用StatefulWidget。

StatefulWidget实例本身是不可变的,并将其可变状态存储在由createState方法创立的独立状态对象中 ,或者者存储在该状态订阅的对象中,例如Stream或者ChangeNotifier对象,其引用存储在StatefulWidget的最终字段中本身。

该框架只需调用一个StatefulWidget就 调用createState,这意味着假如该小部件已经插入到多个位置的树中,那么多个State对象可能与同一个StatefulWidget关联。同样,假如StatefulWidget从树中移除,后来在树再次插入时,框架将调用createState再创立一个新的国家目标,简化的生命周期状态的对象。

  • StatelessWidget

不需要可变状态的小部件。

无状态小部件是一个小部件,它通过构建一系列其余小部件来更加具体地形容客户界面,从而形容客户界面的一部分。构建过程以递归方式继续进行,直到客户界面的形容完全具体(例如,完全由RenderObjectWidget组成,它形容具体的RenderObject)。

当您形容的客户界面部分不依赖于对象本身中的配置信息和其中构件被夸大的BuildContext时,无状态小部件很有用。对于可以动态改变的组合,例如因为具备内部时钟驱动状态,或者取决于某些系统状态,请考虑使用StatefulWidget。

无状态小部件的构建方法通常只在以下三种情况下调用:第一次将小部件插入树中,第一次在小部件的父级更改其配置时以及第二次使用InheritedWidget时,它依赖于更改。

假如一个小部件的父节点会定期更改小部件的配置,或者者假如它依赖于频繁更改的继承小部件,那么优化构建方法的性能以保持流畅的渲染性能非常重要。

有几种技术可以用来最小化重建无状态小部件的影响:

最小化构建方法及其创立的任何小部件传递创立的节点数量。例如,可以考虑只使用一个Align或者一个 CustomSingleChildLayout,而不是精心安排Row s,Column s,Padding s和SizedBox es来定位一个单独的孩子。您可以考虑使用单个CustomPaint小部件,而不是使用多个Container的复杂分层和装饰 s来绘制恰当的图形效果。

const尽可能使用小部件,并为小部件提供const构造函数,以便小部件的客户也可以这样做。
考虑将无状态小部件重构为有状态的小部件,以便它可以使用StatefulWidget中形容的少量技术,例如缓存子树的公共部分,并在更改树结构时使用GlobalKey。

假如因为使用了InheritedWidget,小部件可能会经常重建 ,请考虑将无状态小部件重构为多个小部件,并将更改后的树部分推送到树叶。例如,不是构建一个具备四个小部件的树,最内部的小部件取决于主题,而是考虑将构建最内部小部件的构建函数的部分分解到其自己的小部件中,以便只有最内部的小部件当主题改变时需要重建。

4.2.2 源码分析

Flutter的Widget有StatelessWidget和StatefulWidget两个子类(当然还有其余子类,此处暂且不谈),二者的的使用方式大致模板代码如下:

//StatelessWidget的使用模板代码class StatelessWidgetDemo extends StatelessWidget{  @override  Widget build(BuildContext context) {    return null;///返回创立的页面  }}//StatefulWidget的使用方式模板代码class StatefulWidgetDemo extends StatefulWidget{  @override  State<StatefulWidget> createState() {    //创立state对象    return _State();  }}class _State extends State<StatefulWidgetDemo>{  //创立页面  @override  Widget build(BuildContext context) {    return null;  }}

这是典型的模板设计模式的应用,我们只要要依葫芦画瓢即可以创立所需的UI页
阅读上面的代码,可以跑出一下问题:
1) build方法需要一个BuildContext参数,那么这个BuildContext是什么?
2)build方法是模板方法,那么什么时候调用的呢?
带着这两个问题,后面简单的梳理下Widget的结构,之所以说是简单的梳理,由于难得我也不会,还没研究到。

StatelessWidget和StatefulWidget都继承于Widget,其定义如下:

abstract class Widget extends DiagnosticableTree {  const Widget({ this.key });  final Key key;  @protected  Element createElement();}

Widget继承于DiagnosticableTree,且提供了一个createElement笼统方法返回了一个Element对象,该对象查看源码可知其继承解构是Element extends DiagnosticableTree implements BuildContext.所以其Widget 和Element的整体解构可以用如下图表示:


image.png

先来看看StatelessWidget的具体实现:

abstract class StatelessWidget extends Widget { @override  StatelessElement createElement() => StatelessElement(this);    @protected  Widget build(BuildContext context);}

StatelessWidget实现了createElement方法返回了一个StatelessElement对象,且提供了一个build方法,注意build方法的参数是BuildContext,那么这个BuildContext是不是就是StatelessElement这个对象了呢?预知答案如何先看看build是在那儿调用的,在StatelessElement这个类里可以找到答案,其源码如下:

class StatelessElement extends ComponentElement {  //在element中调用了widget.build方法,并将自己传入了进去  //所以BuildContext就是StatelessElement  @override  Widget build() => widget.build(this);}

通过其源码可以知道StatelessElement继承了ComponentElement,且重写了build方法,其调用了widget的build方法。这个build就是StatelessWidget对象(或者者其子对象),并且可以确定StatelessWidget的build方法的参数就是StatelessElement这个对象。

所以可以断定想要知道StatelessWidget的build(BuildContext)方法什么时候调用,就需要知道StatelessElement的build()什么时候调用。在StatelessElement的父类ComponentElement的perfromReBuild方法可以得到解答:

@override  void performRebuild() {   //省略了部分代码    Widget  built = build();    //省略部分代码  }

所以概述下来就是StatelessWidget通过build(BuildContext)方法构建Widget是通过StatelessElement的build()方法来完成的。想要调用build(BuildContext)必定先通过createElement方法创立一个StatelessElement对象。那么有一个此处就有一个问题了:Widget的createElement方法是神马时候调用的呢?

上面粗略的分了StatelessWidget,下来再来简略的看下StatefullWidget这个类。

abstract class StatefulWidget extends Widget {   @override  StatefulElement createElement() => StatefulElement(this);  @protected  State createState();}

StatefulWidget的createElement方法返回了SatefulElement,且提供了一个createState()方法,大胆猜测一下createState就是在StatefulElement里面调用的,果不其然,证据如下:

StatefulElement 的构造器:   StatefulElement(StatefulWidget widget)    ///调用了createState方法    : _state = widget.createState(), super(widget) {     }

StatefulWidget需要通过createState方法创立一个State,State也提供了build(BuildContext)方法。另外查看StatefulElement的可以该类也实现了ComponentElement的build方法:

@override  Widget build() => state.build(this);

分析到这儿StatelessWidget ,StatefulWidget和Element的关系可以用如下图来表示:


image.png

其构建关系的流程图可以用如下来表示:


image.png
build(BuildContext)方法就需要先调用具体子类的createElement方法创立对应的ComponentElement对象,然后重写Component的build方法。performRebuild方法又是什么时机调用的的呢?performRebuild方法在ComponentElment的mount方法和rebuild方法()方法里面都有调用,而ComponentElement的mount方法又是Flutter形成渲染树的入口:
//mount方法形成理解析Widget,构建渲染树  @override  void mount(Element parent, dynamic newSlot) {    super.mount(parent, newSlot);    _firstBuild();  }   void _firstBuild() { //rebuild方法内部调用了performRebuild方法。    rebuild();  }

收录自|原文地址

喜欢请多多关注~

  • 全部评论(0)
最新发布的资讯信息
【系统环境|】2FA验证器 验证码如何登录(2024-04-01 20:18)
【系统环境|】怎么做才能建设好外贸网站?(2023-12-20 10:05)
【系统环境|数据库】 潮玩宇宙游戏道具收集方法(2023-12-12 16:13)
【系统环境|】遥遥领先!青否数字人直播系统5.0发布,支持真人接管实时驱动!(2023-10-12 17:31)
【系统环境|服务器应用】克隆自己的数字人形象需要几步?(2023-09-20 17:13)
【系统环境|】Tiktok登录教程(2023-02-13 14:17)
【系统环境|】ZORRO佐罗软件安装教程及一键新机使用方法详细简介(2023-02-10 21:56)
【系统环境|】阿里云 centos 云盘扩容命令(2023-01-10 16:35)
【系统环境|】补单系统搭建补单源码搭建(2022-05-18 11:35)
【系统环境|服务器应用】高端显卡再度登上热搜,竟然是因为“断崖式”的降价(2022-04-12 19:47)
手机二维码手机访问领取大礼包
返回顶部