Day10 - Flutter的Widget-Element-RenderObject

简介: Day10 - Flutter的Widget-Element-RenderObject

概述


  • Flutter的渲染流程
  • 对象的创造过程
  • 小部件的键


一、Flutter的渲染流程



  • 1.1、Widget的Element和RenderObject 的关系


image.png


1.2、Widget(窗口小部件) 是什么?


image.png

  • 官方对Widget的说明:
  • Flutter的Widgets的灵感来自React,中心思想是构造你的UI使用这些Widgets。
  • Widget使用配置和状态,描述这个View(界面)应该长什么样的子。
  • 当一个小部件发生改变时,小部件就会重新构建它的描述,框架会和之前的描述进行对比,来决定使用最小的改变(最小变化)在渲染树中,从一个状态到另一个状态。
  • 自己的理解:
  • 口小部件就是一个个描述文件,这些描述文件在我们进行状态改变时会不断的构建。
  • 但是对于渲染对象而言,只会使用最小的增量来更新渲染界面。
  • 1.3、Element(元素)是什么?


image.png

  • 官方对Element的描述:
  • Element是一个小部件的实例,在树中详细的位置。
  • 窗口小部件描述和配置子树的样子,而元素实际去配置在元素树中特定的位置。
  • 1.4、RenderObject(渲染对象),官方对RenderObject的描述:
  • 渲染树上的一个对象
  • RenderObject层是渲染库的核心。


二、对象的创造过程



我们这里以Padding为例,Padding设置设置内边距

  • 2.1、Widget(小部件)填充是一个小部件,并且继承自SingleChildRenderObjectWidget继承关系如下:


Padding -> SingleChildRenderObjectWidget -> RenderObjectWidget -> Widget
  • 我们之前在创建Widget时,经常使用StatelessWidget和StatefulWidget,这种Widget只是将其他的Widget在构建方法中组装起来,并不是一个真正可以渲染的Widget(在之前的课程中其实有提到)。在Padding的类中,我们发现任何和渲染相关的代码,这是因为Padding只是一个配置信息,这个配置信息会转移我们设置的属性不同,可以替换的销毁和创建。
  • 问题:不断的销毁和创造会不会影响Flutter的性能呢?
    答:并不会,答案在另一篇文章中:你不必担心Dart的垃圾回收器
  • 那么真正的渲染相关的代码在哪里执行呢?
    答:渲染对象
  • 2.2、渲染对象我们来看Padding里面的代码,有一个非常重要的方法:
  • 这个方法其实是来自RenderObjectWidget的类,在这个类中它是一个抽象方法;
  • 抽象方法是必须被子类实现的,但是它的子类SingleChildRenderObjectWidget也是一个抽象类,所以可以不实现父类的抽象方法
  • 但是Padding不是一个抽象类,必须在这里实现对应的抽象方法,而它的实现就是下面的实现


@override
RenderPadding createRenderObject(BuildContext context) {
    return RenderPadding(
        padding: padding,
        textDirection: Directionality.of(context),
    );
}
  • 顶部的代码创建了什么呢?RenderPadding的继承关系是什么呢?


RenderPadding -> RenderShiftedBox -> RenderBox -> RenderObject
  • 我们来具体查看一下RenderPadding的源代码:
  • 如果预设的_padding和原来保存的value一样,那么直接返回;
  • 如果绝对,调用_markNeedResolution,而_markNeedResolution内部调用了markNeedsLayout;
  • 而markNeedsLayout的目的就是标记在下一帧布局时,需要重新布局performLayout;
  • 如果我们找的是Opacity,那么RenderOpacity是调用markNeedsPaint,RenderOpacity中是有一个油漆方法的;


set padding(EdgeInsetsGeometry value) {
    assert(value != null);
    assert(value.isNonNegative);
    if (_padding == value)
         return;
         _padding = value;
         _markNeedResolution();
    }
}


  • 2.3、Element(元件)我们来思考一个问题:
  • 我们写的大量的Widget在树结构中存在引用关系,但是Widget会被不断的销毁和重建,则意味着这棵树非常不稳定;
  • 那么由谁来维系整个Flutter应用程序的树形结构的稳定呢?
    答案就是元素。
  • 官方的描述:Element是一个Widget的实例,在树中详细的位置。
  • 元素什么时候创造?在每一次创建Widget的时候,会创建一个对应的Element,然后放入元素插入树中。
  • Element保存着对Widget的引用;
  • 在SingleChildRenderObjectWidget中,我们可以找到如下代码:
  • 在Widget中,元素被创造,并且在创造时,将this(Widget)设定了;
  • Element就保存了对Widget的应用;


@override
SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);


  • 在创建完一个元素之后,框架会调用安装方法来将元素插入到树中具体的位置:


image.png


  • 在调用mount方法时,会同时使用Widget来创建RenderObject,并且保持对RenderObject的引用:_renderObject = widget.createRenderObject(this);


@override
void mount(Element parent, dynamic newSlot) {
   super.mount(parent, newSlot);
   _renderObject = widget.createRenderObject(this);
   assert(() {
       _debugUpdateRenderObjectOwner();
       return true;
   }());
   assert(_slot == newSlot);
   attachRenderObject(newSlot);
   _dirty = false;
}
  • 但是,如果您去看一下Text这种组合类的小部件,它也会执行mount方法,但是mount方法中并没有调用createRenderObject这样的方法。
  • 我们发现ComponentElement最主要的目的是挂载之后,调用_firstBuild方法


@override
void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    assert(_child == null);
    assert(_active);
    _firstBuild();
    assert(_child != null);
}
void _firstBuild() {
    rebuild();
}
  • 如果是一个StatefulWidget,则创建出来的是一个StatefulElement,我们来看一下StatefulElement的构造器:
  • 调用widget的createState()
  • 所以StatefulElement对创建出来的State是有一个引用的
  • 而_state又对widget有一个引用


StatefulElement(StatefulWidget widget)
: _state = widget.createState(),
....省略代码
_state._widget = widget;
  • 而调用build的时候,本质上调用的是_state中的build方法:


Widget build() => state.build(this);
  • 2.4、build 的 context(上下文) 是什么
    在StatelessElement中,我们发现是将这个合并,所以本质上BuildContext就是当前的Element


Widget build() => widget.build(this);
  • 我们来看一下继承关系图:元素是实现了BuildContext类(隐式接口)


abstractclass Element extends DiagnosticableTree implements BuildContext
  • 在StatefulElement中,build方法也是类似,调用state的build方式时,引用的是this


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


  • 2.5、创建过程小结
  • 窗口小部件只是描述了配置信息:
  • 其中包含createElement方法用于创建元素
  • 也包含createRenderObject,但不是自己在调用
  • 元素是真正的保存树结构的对象:
  • 创建出来后会由framework调用mount方法;
  • 在mount方法中会调用widget的createRenderObject对象;
  • 并且Element对widget和RenderObject都有引用;
  • RenderObject 是真正渲染的对象:其中有 markNeedsLayoutperformLayoutmarkNeedsPaintpaint 等方法


三、小部件的键



在我们创造的小部件的时候,总是会看到一个关键的参数,它又是做什么的呢?

  • 3.1、key的案例需求


image.png


class _HYHomePageState extends State<HYHomePage> {
    List<String> names = ["aaa", "bbb", "ccc"];
    @override
    Widget build(BuildContext context) {
        return Scaffold(
           appBar: AppBar(
              title: Text("Test Key"),
           ),
           body: ListView(
              children: names.map((name) {
                 return ListItemLess(name);
              }).toList(),
           ),
           floatingActionButton: FloatingActionButton(
             child: Icon(Icons.delete),
             onPressed: () {
                setState(() {
                   names.removeAt(0);
                });
             }
           ),
      );
   }
}
  • 3.2、StatelessWidget的实现
    我们先对ListItem使用一个StatelessWidget进行实现:


class ListItemLess extends StatelessWidget {
   finalString name;
   final Color randomColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));
   ListItemLess(this.name);
   @override
   Widget build(BuildContext context) {
        return Container(
             height: 60,
             child: Text(name),
             color: randomColor,
        );
    }
}


  • 它的实现效果是每删除一个,所有的颜色都会发现一次变化,原因非常简单,删除之后调用setState,会重新构建,重新构建出来的新的StatelessWidget会重新生成一个新的随机颜色


image.png

3.3、StatefulWidget的实现(没有键)

class ListItemFul extends StatefulWidget {
    finalString name;
    ListItemFul(this.name): super();
    @override
    _ListItemFulState createState() => _ListItemFulState();
}
class _ListItemFulState extends State<ListItemFul> {
    final Color randomColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));
    @override
    Widget build(BuildContext context) {
       return Container(
           height: 60,
           child: Text(widget.name),
           color: randomColor,
       );
    }
}


我们发现一个很奇怪的现象,颜色不变化,但是数据向上移动了

  • 这是因为在删除第一条数据的时候,Widget对应的元素并没有改变;
  • 而元素中对应的状态引用也没有发生改变;
  • 在更新Widget的时候,Widget使用了没有改变的Element中的State;


image.png


3.4、StatefulWidget的实现(随机键)

我们使用一个随机的key,ListItemFul的修改如下:

class ListItemFul extends StatefulWidget {
    finalString name;
    ListItemFul(this.name, {Key key}): super(key: key);
    @override
    _ListItemFulState createState() => _ListItemFulState();
}

主页界面代码修改如下:

body: ListView(
  children: names.map((name) {
    return ListItemFul(name, key: ValueKey(Random().nextInt(10000)),);
  }).toList(),
),

这一次我们发现,每次删除都会出现随机颜色的现象:这是因为修改了key之后,Element会强制刷新,然后对应的State也会重新创建

// Widget类中的代码
staticbool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key;
 }


image.png

3.5、StatefulWidget的实现(名称为键)

这次,我们将名称作为key来看一下结果

body: ListView(
   children: names.map((name) {
      return ListItemFul(name, key: ValueKey(name));
   }).toList(),
),


我们理想中的效果:

  • 因为这是在更新widget的过程中根据key进行了diff算法
  • 在前后进行对比时,发现bbb对应的元素和ccc对应的元素会继续使用,那么就会删除之前aaa对应的元素,而不是直接删除最后一个元素


image.png


3.6、钥匙的分类


Key本质是一个抽象,不过它也有一个工厂构造器,创建出来一个ValueKey

直接子类主要有:LocalKey和GlobalKey


  • LocalKey,它具有相同父元素的小部件进行比较,也是diff算法的核心所在;
  • GlobalKey,通常我们会使用GlobalKey某个Widget对应的Widget或State或Element
  • 3.6.1、本地密钥LocalKey有三个子类
  • ValueKey:是当我们以特定的值作为键时使用,某些一个字符串,数字等等
  • ObjectKey对象关键字:如果两个学生,他们的名字一样,使用name作为他们的key就不合适了;我们可以创建出一个学生对象,使用对象来作为key
  • UniqueKey唯一键:如果我们要确保key的唯一性,可以使用UniqueKey;例如我们之前使用随机数来保证key的不同,这里我们就可以换成UniqueKey;
  • 3.6.2、全局密钥
    GlobalKey可以帮助我们访问某个Widget的信息,包括Widget或State或Element等对象
    我们来看下面的例子:我希望可以在HYHomePage中直接访问HYHomeContent中的内容


class HYHomePage extends StatelessWidget {
     final GlobalKey<_HYHomeContentState> homeKey = GlobalKey();
     @override
     Widget build(BuildContext context) {
         return Scaffold(
             appBar: AppBar(
                title: Text("列表测试"),
             ),
             body: HYHomeContent(key: homeKey),
             floatingActionButton: FloatingActionButton(
                 child: Icon(Icons.data_usage),
                 onPressed: () {
                    print("${homeKey.currentState.value}");
                    print("${homeKey.currentState.widget.name}");
                    print("${homeKey.currentContext}");
                 },
             ),
        );
   }
}
class HYHomeContent extends StatefulWidget {
     finalString name = "123";
     HYHomeContent({Key key}): super(key: key);
     @override
     _HYHomeContentState createState() => _HYHomeContentState();
}
class _HYHomeContentState extends State<HYHomeContent> {
     finalString value = "abc";
     @override
     Widget build(BuildContext context) {
        return Container();
     }
}


目录
相关文章
|
算法 前端开发
Flutter的Widget-Element-RenderObject
Flutter的Widget-Element-RenderObject
196 0
Flutter的Widget-Element-RenderObject
|
2月前
|
Android开发 iOS开发 容器
鸿蒙harmonyos next flutter混合开发之开发FFI plugin
鸿蒙harmonyos next flutter混合开发之开发FFI plugin
|
29天前
|
开发框架 Dart 前端开发
Flutter 是谷歌推出的一款高效跨平台移动应用开发框架,使用 Dart 语言,具备快速开发、跨平台支持、高性能、热重载及美观界面等特点。
Flutter 是谷歌推出的一款高效跨平台移动应用开发框架,使用 Dart 语言,具备快速开发、跨平台支持、高性能、热重载及美观界面等特点。本文从 Flutter 简介、特点、开发环境搭建、应用架构、组件详解、路由管理、状态管理、与原生代码交互、性能优化、应用发布与部署及未来趋势等方面,全面解析 Flutter 技术,助你掌握这一前沿开发工具。
56 8
|
29天前
|
存储 JavaScript 前端开发
在Flutter开发中,状态管理至关重要。随着应用复杂度的提升,有效管理状态成为挑战
在Flutter开发中,状态管理至关重要。随着应用复杂度的提升,有效管理状态成为挑战。本文介绍了几种常用的状态管理框架,如Provider和Redux,分析了它们的基本原理、优缺点及适用场景,并提供了选择框架的建议和使用实例,旨在帮助开发者提高开发效率和应用性能。
36 4
|
29天前
|
传感器 前端开发 Android开发
在 Flutter 开发中,插件开发与集成至关重要,它能扩展应用功能,满足复杂业务需求
在 Flutter 开发中,插件开发与集成至关重要,它能扩展应用功能,满足复杂业务需求。本文深入探讨了插件开发的基本概念、流程、集成方法、常见类型及开发实例,如相机插件的开发步骤,同时强调了版本兼容性、性能优化等注意事项,并展望了插件开发的未来趋势。
40 2
|
2月前
|
开发者
鸿蒙Flutter实战:07-混合开发
鸿蒙Flutter混合开发支持两种模式:1) 基于har包,便于主项目开发者无需关心Flutter细节,但不支持热重载;2) 基于源码依赖,利于代码维护与热重载,需配置Flutter环境。项目结构包括AppScope、flutter_module等目录,适用于不同开发需求。
103 3
|
1月前
|
传感器 开发框架 物联网
鸿蒙next选择 Flutter 开发跨平台应用的原因
鸿蒙(HarmonyOS)是华为推出的一款旨在实现多设备无缝连接的操作系统。为了实现这一目标,鸿蒙选择了 Flutter 作为主要的跨平台应用开发框架。Flutter 的跨平台能力、高性能、丰富的生态支持和与鸿蒙系统的良好兼容性,使其成为理想的选择。通过 Flutter,开发者可以高效地构建和部署多平台应用,推动鸿蒙生态的快速发展。
235 0
|
1月前
|
Dart 安全 UED
Flutter&鸿蒙next中的表单封装:提升开发效率与用户体验
在移动应用开发中,表单是用户与应用交互的重要界面。本文介绍了如何在Flutter中封装表单,以提升开发效率和用户体验。通过代码复用、集中管理和一致性的优势,封装表单组件可以简化开发流程。文章详细讲解了Flutter表单的基础、封装方法和表单验证技巧,帮助开发者构建健壮且用户友好的应用。
80 0
|
2月前
|
开发框架 移动开发 Android开发
安卓与iOS开发中的跨平台解决方案:Flutter入门
【9月更文挑战第30天】在移动应用开发的广阔舞台上,安卓和iOS两大操作系统各自占据半壁江山。开发者们常常面临着选择:是专注于单一平台深耕细作,还是寻找一种能够横跨两大系统的开发方案?Flutter,作为一种新兴的跨平台UI工具包,正以其现代、响应式的特点赢得开发者的青睐。本文将带你一探究竟,从Flutter的基础概念到实战应用,深入浅出地介绍这一技术的魅力所在。
92 7
|
2月前
|
编解码 Dart API
鸿蒙Flutter实战:06-使用ArkTs开发Flutter鸿蒙插件
本文介绍了如何开发一个 Flutter 鸿蒙插件,实现 Flutter 与鸿蒙的混合开发及双端消息通信。通过定义 `MethodChannel` 实现 Flutter 侧的 token 存取方法,并在鸿蒙侧编写 `EntryAbility` 和 `ForestPlugin`,使用鸿蒙的首选项 API 完成数据的读写操作。文章还提供了注意事项和参考资料,帮助开发者更好地理解和实现这一过程。
116 0