【Flutter从入门到实战】⑬、Flutter渲染流程、Widget-Element-RenderObject的关系、widget的key的使用、Key的分类

【Flutter从入门到实战】⑬、Flutter渲染流程、Widget-Element-RenderObject的关系、widget的key的使用、Key的分类,第1张

Flutter从入门到实战
一共分为23个系列
①(Flutter、Dart环境搭建篇) 共3个内容 已更新
②(Dart语法1 篇) 共4个内容 已更新
③(Dart语法2 篇) 共2个内容 已更新
④(Flutter案例开发篇) 共4个内容 已更新
⑤(Flutter的StatelessWidget 共3个内容 已更新
⑥(Flutter的基础Widget篇) 共2个内容 已更新
⑦(布局Widget篇) 共1个内容 已更新
⑧(Flex、Row、Column以及Flexible、Stack篇) 共1个内容 已更新
⑨(滚动的Widget篇) 共4个内容 已更新
⑩(Dart的Future和网络篇) 共3个内容 已更新
⑪(豆瓣案例-1篇) 共3个内容 已更新
⑫(豆瓣案例-2篇) 共3个内容 已更新
⑬(Flutter渲染流程篇) 共3个内容 已更新

官方文档说明

官方视频教程
Flutter的YouTube视频教程-小部件


⑬、Flutter渲染流程篇 📚小贴士小贴士1. dart的打印log封装 打印所在文件以及所在行log.dart 小贴士1. dart打印log的使用 ①、三棵树-Widget-Element-RenderObject的关系1. Widget是什么2. Element是什么3. RenderObject是什么4.总结 ②、widget的key的使用1. 未绑定key 引发的问题如何防止颜色发生改变这个问题出现正确流程 但是不是我们想要的效果 2. 绑定key的作用3. Key的分类3.1 LocalKey3.2 GlobalKey3.2 案例代码-GlobalKey

📚小贴士 小贴士1. dart的打印log封装 打印所在文件以及所在行 log.dart
void yhLog(Object message, StackTrace current) {
  YHCustomTrace programInfo = YHCustomTrace(current);
  print("所在文件: ${programInfo.fileName}, 所在行: ${programInfo.lineNumber}, 打印信息: $message");
}

class YHCustomTrace {
  final StackTrace? _trace;

  String fileName = "";
  int lineNumber = 0;
  int columnNumber = 0;

  YHCustomTrace(this._trace) {
    _parseTrace();
  }

  void _parseTrace() {
    var traceString = this._trace.toString().split("\n")[0];
    var indexOfFileName = traceString.indexOf(RegExp(r'[A-Za-z_]+.dart'));
    var fileInfo = traceString.substring(indexOfFileName);
    var listOfInfos = fileInfo.split(":");
    this.fileName = listOfInfos[0];
    this.lineNumber = int.parse(listOfInfos[1]);
    var columnStr = listOfInfos[2];
    columnStr = columnStr.replaceFirst(")", "");
    this.columnNumber = int.parse(columnStr);
  }
}
小贴士1. dart打印log的使用
yhLog("首页网络请求测试 -- ${res}", StackTrace.current);

log的打印信息效果


①、三棵树-Widget-Element-RenderObject的关系

Flutter 渲染都需要经过layoutpaint 过程
Widget 是没有经过 layoutpaint 过程
那么Widget最终是通过RenderObject去渲染出来的
Widget-Element-RenderObject关系
相当于
HTML 转换成 虚拟DOM 再转换成 真实DOM
HTML > 虚拟DOM > 真实DOM
虚拟 DOM 到底是什么,说简单点,就是一个普通的 JavaScript 对象,包含了 tag、props、children 三个属性。
具体了解虚拟DOM可以了解这篇文章 https://juejin.cn/post/6844903870229905422

HTML


hello world!!!

上面的 HTML 转换为虚拟 DOM 如下:

{
 tag: 'div',
 props: {
  id: 'app'
 },
 chidren: [
   {
    tag: 'p',
    props: {
      className: 'text'
    },
    chidren: [
      'hello world!!!'
    ]
  }
]
}

我们继续回到讲解 Widget-Element-RenderObject 关系
Widget-Element-RenderObject每个都有其对应的key
拿到Widget的key和Element的key 进行一个对比 判断它们的类型和状态是否相同
如果相同 就不需要更新对应key的RenderObject
最少的开销更新 对应的RenderObject
,你想具体更加深入key和三者之间的关系可以查看下面的文档或者视频讲解
查看key的具体文档和视频讲解
key的官方文档
key的官方教程视频讲解
👇 如果上面访问不了那么你可以通过我B站👇的视频查看

When to Use Keys - Flutter Widgets 101 Ep. 4

1. Widget是什么

Widget官网文档说明

Flutter 小部件是使用从React中汲取灵感的现代框架构建的。中心思想是你用小部件构建你的 UI。小部件描述了给定当前配置和状态的视图应该是什么样子。当小部件的状态发生变化时,小部件会重建其描述,框架会根据之前的描述进行比较,以确定底层渲染树从一种状态转换到下一种状态所需的最小更改。

2. Element是什么

Element是什么
一个抽象类,所有 HTML 元素都对其进行扩展。

3. RenderObject是什么

RenderObject是什么
渲染树中的一个对象。
RenderObject类层次结构是渲染库存在的核心。
RenderObject有一个parent,并且有一个称为parentData的槽,父RenderObject可以在其中存储特定于子的数据,例如子位置。RenderObject类还实现了基本的布局和绘制协议。
然而, RenderObject类没有定义子模型(例如,一个节点是否有零个、一个或多个子节点)。它也没有定义坐标系(例如,孩子是否位于笛卡尔坐标、极坐标等中)或特定的布局协议(例如,布局是宽度进高还是大小限制-out,或者父级是否在子级布局之前或之后设置子级的大小和位置等;或者实际上是否允许子级读取其父级的parentData插槽)。
RenderBox子类引入了布局系统使用笛卡尔坐标的观点。

4.总结

我们可以到系统查看 一些 Widget
比如

Container、Text、自己封装的widget 这些Widget都不会生成RenderObject
虽然Widget 不会创建RenderObject, 但是Widget的类有一个抽象方法是
createElement
Padding、Row 这些Widget 都是属于渲染Widget 继承链最终继承于RenderObject 或者是生成 RenderObject
结论: 
一、不是渲染的RenderWidget
1.所有的Widget都会创建Element 
     有些是StateLessElement  有些是StateFulElement 
2. 渲染的Widget都会创建 RenderObjectElement 
3. ComponentElement最终调用mount方法   
4. mount 调用了 _firstBuild(); 方法
5. 调用了 rebuild()  调用了 performRebuild    调用了 Widget的build
6. 所有的Widget创建的时候 都会调用 build方法

二、渲染的ReaderWidget
通过ReanderObjectElement : mount -> _widget.createRanderObject

不是渲染的RenderWidget 和 渲染的ReaderWidget 区别是创建的方式不一样

三、Element 有两个属性 一个是 widget 、一个是_readerObject

四、StateLessElement 和 StateFulElement 
StateFulElement
构造方法中 _state = widget.createState()
_state.widget = widget
这就是我们为什么可以在State里面通过this.widget获取我们上层的widget


五、简单总结

widget通过底层去创建一个Element
Element通过mount方法 
如果 不是渲染的RenderElement 回到Widget去调用build方法
如果 是渲染的RenderElement 去调用createRanderObject方法

上面还会根据Element 是不是StateFulElement
如果是 StateFulElement 还会去widget.createState()
	
widget通过build 都会有一个回调 BuildContext content 
BuildContext content 实际上就是 Element
通过Element知道是哪个widget 。可以拿到它 往上查找信息


②、widget的key的使用

查看key的具体文档和视频讲解

通过一个案例来讲解widget的key

一个页面有3个Widget 背景颜色是随机的。还一个悬浮按钮。通过点击按钮删除最顶层的Widget 并且对应的Widget 颜色是不会变的

1. 未绑定key 引发的问题

下面的代码是通过点击之后 删除第一个数据

import 'dart:math';

import 'package:flutter/material.dart'; // runApp在这个material库里面

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: YHiOSHomePage(),
    );
  }
}

class YHiOSHomePage extends StatefulWidget {
  @override
  State<YHiOSHomePage> createState() => _YHiOSHomePageState();
}

class _YHiOSHomePageState extends State<YHiOSHomePage> {
  final List<String> names = ["aaa","bbb","ccc"];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("列表测试"),
      ),
      body: ListView(
        children: names.map((item){
          return ListItemLess(item);
        }).toList(),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.delete),
        onPressed: (){
          // setState 会重新build
        setState(() {
          names.removeAt(0);
        });
        },
      ),
    );
  }
}

class ListItemLess extends StatelessWidget {

  final String name;
  final Color randColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));
  ListItemLess(this.name);
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(name),
      height: 90,
      color: randColor,
    );
  }
}


通过上面的Gif动画我们发现
我们每删除一个 发现Widget的背景颜色都发生变化。
这个是 SetSate每次都会进行一个build *** 作

如何防止颜色发生改变这个问题出现

我们尝试一下将 StatelessWidget 改成 StatefulWidget

我们发现神奇的事情就是 我们删除的是第一个。但是看到最顶部的颜色不变。删掉了最底部的
这个其实是复用问题。

正确流程 但是不是我们想要的效果

其实就是我们的Widget 删除了第一个
Element并且是StateFulElement的
Element针对Widget进行了创建。并且每个Element都包含了state
然后通过Widget删除第一个。
Element之前因为存在了3个state。然后参看Widget当前有多少Widget
当前Widget存在2个。所以Element保留前面的 删掉最后一个

2. 绑定key的作用

ListItemFul(this.name ,{Key? key}): super(key:key); // 绑定
ListItemFul(item,key: ValueKey(item),);// 使用

import 'dart:math';

import 'package:flutter/material.dart'; // runApp在这个material库里面

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: YHiOSHomePage(),
    );
  }
}

class YHiOSHomePage extends StatefulWidget {
  @override
  State<YHiOSHomePage> createState() => _YHiOSHomePageState();
}

class _YHiOSHomePageState extends State<YHiOSHomePage> {
  final List<String> names = ["aaa","bbb","ccc"];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("列表测试"),
      ),
      body: ListView(
        children: names.map((item){
			   // (需求很少)如果删除 向重新构建ListItemFul 可以给key绑定一个随机数 但是随机数还是有可能是相同的。所以可以使用UniqueKey()
          // 数据names是唯一的,不用害怕被改变。
          // return ListItemFul(item,key: ValueKey(item),); // ⭐️常用
          // return ListItemFul(item,key: ValueKey(Random().nextInt(10000)),);
          return ListItemFul(item,key: UniqueKey(),);
        }).toList(),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.delete),
        onPressed: (){
          // setState 会重新build
        setState(() {
          names.removeAt(0);
        });
        },
      ),
    );
  }
}

class ListItemLess extends StatelessWidget {

  final String name;
  final Color randColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));
  ListItemLess(this.name);
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(name),
      height: 90,
      color: randColor,
    );
  }
}

// 使用StateFul的Widget
class ListItemFul extends StatefulWidget {

  final String name;

  // 绑定key
  ListItemFul(this.name ,{Key? key}): super(key:key);

  @override
  State<ListItemFul> createState() => _ListItemLessState();
}

class _ListItemLessState extends State<ListItemFul> {
  final Color randColor = Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256));

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text(widget.name),
      height: 90,
      color: randColor,
    );
  }
}

3. Key的分类 3.1 LocalKey

ValueKey-泛型key、ObjectKey-对象key、UniqueKey - 唯一的key

3.2 GlobalKey

全局的key 作用是 : GlobalKey 能访问某个Widget的信息
案例 : 向通过悬浮按钮点击 拿到HomePage和HomeContent的值
1.可以是用静态变量 但是静态变量 是类的共享属性的
我们需要的是对象属性
2. 使用GlobalKey

3.2 案例代码-GlobalKey
import 'dart:math';

import 'package:flutter/material.dart'; // runApp在这个material库里面

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: YHHomePage(),
    );
  }
}

class YHHomePage extends StatelessWidget {
  // 声明一个GlobalKey
  // ⭐️GlobalKey 能访问某个Widget的信息
  final GlobalKey<_YHHomeContentState> homeKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("列表测试"),
      ),
      body:YHHomeContent(key:homeKey),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.gesture),
        onPressed: (){
          print(homeKey.currentState?.message);
          print(homeKey.currentState?.widget.name);
        },
      ),
    );

  }
}




class YHHomeContent extends StatefulWidget {
   final String name = "宇夜iOS";
   YHHomeContent({Key? key}) : super(key: key);
  @override
  State<YHHomeContent> createState() => _YHHomeContentState();
}

class _YHHomeContentState extends State<YHHomeContent> {
   final String message = "123";
  @override
  Widget build(BuildContext context) {
    return Text(message);
  }
}




欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/997113.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存