当前位置: 首页 > news >正文

flutter 专题四 Flutter渲染流程

一、 Widget - Element - RenderObject关系

二、 Widget 、Element 、RenderObject 分别表示什么

2.1 Widget 

     Widget描述和配置子树的样子

  • Widget就是一个个描述文件,这些描述文件在我们进行状态改变时会不断的build。
  • 但是对于渲染对象来说,只会使用最小的开销来更新渲染界面

2.2、Element 

  • Element是一个Widget的实例,在树中详细的位置。
  • Widget描述和配置子树的样子,而Element实际去配置在Element树中特定的位置

2.3、RenderObject

  • 渲染树上的一个对象。
  • RenderObject层是渲染库的核心

三、js生成的HTML代码和Element的理解

Element其实就相当于React中的虚拟DOM,我们先来理解一下前端里面的虚拟DOM。

当我们书写js生成的HTML代码,这时候会直接操作真实的DOM,操作真实DOM是非常消耗性能的,所以React和Vue都有虚拟DOM的概念,什么意思呢?就是当我们通过js操作HTML,我们会先去操作虚拟DOM,虚拟DOM中通过diff算法,判断哪些DOM需要修改,甚至不需要修改,最后把虚拟DOM打个补丁到真实DOM上,这样做的好处就是我们可以以最小的开销来更新真实的DOM。

我们再看一下上面的三棵树,Widget就相当于HTML代码,Element就相当于虚拟DOM,Render就相当于真实DOM;

当我们创建一个Widget的时候,我们也许就不需要创建一个新的Render对象,我们先去看看保存的Element的类型和key是否一致,如果一致,就直接修改属性即可,这样我们就没必要创建新的Render Object,也许只是修改其中某个属性就行,这样就做到了以最小的开销来更新Render Object。

四 、Flutter 的渲染流程大致如下

4.1 构建widget树:首先,你的应用程序会构建一个由widget组成的树。这些widget描述了应用程序的用户界面。

我们先给widget做个分类:

这些是组件Widget,不会生成RenderObject
Container()
Text()
HYHomeContent()

这些是渲染Widget,会生成RenderObject
Padding()
Row()

我们这里以Padding为例,Padding是用来设置内边距,我们看看这个Widget最后怎么生成RenderObject的。

2.1. Widget

Padding是一个Widget,并且继承自SingleChildRenderObjectWidget

继承关系如下:

Padding  -> SingleChildRenderObjectWidget -> RenderObjectWidget -> Widget

Container继承关系如下:

Container -> StatelessWidget -> Widget

我们之前在创建Widget时,经常使用StatelessWidget和StatefulWidget,这种Widget只是将其他的Widget在build方法中组装起来,并不是一个真正可以渲染的Widget(在之前的课程中其实有提到)。

在Padding的类中,我们找不到任何和渲染相关的代码,这是因为Padding仅仅作为一个配置信息,这个配置信息会随着我们设置的属性不同,频繁的销毁和创建。

问题:频繁的销毁和创建会不会影响Flutter的性能呢?

  • 并不会,答案在我的另一篇文章中;
  • https://mp.weixin.qq.com/s/J4XoXJHJSmn8VaMoz3BZJQ

那么真正的渲染相关的代码在哪里执行呢?

  • RenderObjectWidget

2.2. RenderObjectWidget

我们来看Padding里面的代码,有一个非常重要的方法:

  • 这个方法其实是来自RenderObjectWidget的类,在这个类中它是一个抽象方法;
  • 抽象方法是必须被子类实现的,但是它的子类SingleChildRenderObjectWidget也是一个抽象类,所以可以不实现父类的抽象方法;
  • 但是Padding不是一个抽象类,必须在这里实现对应的抽象方法,而它的实现就是下面的实现;
@override
RenderPadding createRenderObject(BuildContext context) {return RenderPadding(padding: padding,textDirection: Directionality.of(context),);
}

上面的代码创建了什么呢?RenderPadding

RenderPadding的继承关系是什么呢?

RenderPadding -> RenderShiftedBox -> RenderBox -> RenderObject

我们来具体查看一下RenderPadding的源代码:

  • 如果传入的_padding和原来保存的value一样,那么直接return;
  • 如果不一致,调用_markNeedResolution,而_markNeedResolution内部调用了markNeedsLayout;
  • 而markNeedsLayout的目的就是标记在下一帧绘制时,需要重新布局performLayout;
  • 如果我们找的是Opacity,那么RenderOpacity是调用markNeedsPaint,RenderOpacity中是有一个paint方法的;
  set padding(EdgeInsetsGeometry value) {assert(value != null);assert(value.isNonNegative);if (_padding == value)return;_padding = value;_markNeedResolution();}

总结: Widget只是描述了配置信息:

  • 其中包含createElement方法用于创建Element;
  • 也包含createRenderObject,但是不是自己在调用;

4.2 构建element树:然后,每个widget会被转换成一个element。这些element也构成了一棵树。

我们来思考一个问题:

  • 之前我们写的大量的Widget在树结构中存在引用关系,但是Widget会被不断的销毁和重建,那么意味着这棵树非常不稳定;
  • 那么由谁来维系整个Flutter应用程序的树形结构的稳定呢?
  • 答案就是Element。
  • 官方的描述:Element是一个Widget的实例,在树中详细的位置。

我们再研究Padding是怎么创建Element的,我们进入Widget类里面,发现有个createElement()方法:

@protected
@factory
Element createElement();

因为Widget是个抽象类,所以createElement方法必须被它的子类实现。我们也可以得出一个结论,只要你是一个widget,无论是不是渲染的widget,都要实现createElement方法,只不过每个类实现的不一样。

我们发现,对于Padding,是父类SingleChildRenderObjectWidget实现了这个方法,最后返回的是SingleChildRenderObjectElement。

 
  1. @override

  2. SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);

对于Container,也是它的父类StatelessWidget实现了createElement方法:

 
@overrideStatelessElement createElement() => StatelessElement(this);

同理,StatefulWidget也实现了createElement方法:

@overrideStatefulElement createElement() => StatefulElement(this);
它们返回的对象不同,一个是StatelessElement,一个是StatefulElement,只不过都继承于ComponentElement。它们的区别就是StatefulElement会多一个state属性。

小总结

  1. 我们写一个widget
  2. 对于渲染widget会创建RenderObject
  3. 每一个widget都会创建一个Element对象
  4. 在创建完一个Element之后,Flutter引擎会调用mount方法来将Element插入到树中具体的位置

Element什么时候创建?

在每一次创建Widget的时候,会创建一个对应的Element,然后将该元素插入树中。

在SingleChildRenderObjectWidget中,我们可以找到如下代码:

  • 在Widget中,Element被创建,并且在创建时,将this(Widget)传入了,Element就保存了对Widget的应用;
@override
SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);

在创建完一个Element之后,Flutter引擎会调用mount方法来将Element插入到树中具体的位置,再Element类中我们会找到如下代码:

进入ComponentElement源码,查看ComponentElement的mount的执行过程,代码比较繁琐,可以直接看下面总结。

abstract class ComponentElement extends Element {/// Creates an element that uses the given widget as its configuration.ComponentElement(super.widget);Element? _child;bool _debugDoingBuild = false;@overridebool get debugDoingBuild => _debugDoingBuild;@override// 1. 调用mount方法void mount(Element? parent, Object? newSlot) {super.mount(parent, newSlot);assert(_child == null);assert(_lifecycleState == _ElementLifecycle.active);// 2. 调用_firstBuild_firstBuild();assert(_child != null);}void _firstBuild() {// StatefulElement overrides this to also call state.didChangeDependencies.// 3. 调用rebuildrebuild(); // This eventually calls performRebuild.}/// Calls the [StatelessWidget.build] method of the [StatelessWidget] object/// (for stateless widgets) or the [State.build] method of the [State] object/// (for stateful widgets) and then updates the widget tree.////// Called automatically during [mount] to generate the first build, and by/// [rebuild] when the element needs updating.@override@pragma('vm:notify-debugger-on-exception')// 6. 这是performRebuildvoid performRebuild() {assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));// 8.  就是这个WidgetWidget? built;try {assert(() {_debugDoingBuild = true;return true;}());// 7. 调用build方法生成一个Widgetbuilt = build();assert(() {_debugDoingBuild = false;return true;}());debugWidgetBuilderValue(widget, built);} catch (e, stack) {_debugDoingBuild = false;built = ErrorWidget.builder(_debugReportException(ErrorDescription('building $this'),e,stack,informationCollector: () => <DiagnosticsNode>[if (kDebugMode)DiagnosticsDebugCreator(DebugCreator(this)),],),);} finally {// We delay marking the element as clean until after calling build() so// that attempts to markNeedsBuild() during build() will be ignored._dirty = false;assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));}try {_child = updateChild(_child, built, slot);assert(_child != null);} catch (e, stack) {built = ErrorWidget.builder(_debugReportException(ErrorDescription('building $this'),e,stack,informationCollector: () => <DiagnosticsNode>[if (kDebugMode)DiagnosticsDebugCreator(DebugCreator(this)),],),);_child = updateChild(null, built, slot);}}/// Subclasses should override this function to actually call the appropriate/// `build` function (e.g., [StatelessWidget.build] or [State.build]) for/// their widget.@protectedWidget build();@overridevoid visitChildren(ElementVisitor visitor) {if (_child != null) {visitor(_child!);}}@overridevoid forgetChild(Element child) {assert(child == _child);_child = null;super.forgetChild(child);}
}// 4. 这是rebuild
void rebuild() {assert(_lifecycleState != _ElementLifecycle.initial);if (_lifecycleState != _ElementLifecycle.active || !_dirty) {return;}Element? debugPreviousBuildTarget;performRebuild();
}/// Cause the widget to update itself.
///
/// Called by [rebuild] after the appropriate checks have been made.
@protected
// 5. 调用performRebuild
void performRebuild();
}class StatelessElement extends ComponentElement {/// Creates an element that uses the given widget as its configuration.StatelessElement(StatelessWidget super.widget);@override// 9. 拿到widget,调用widget的build方法// 这个widget就是创建element的时候传进来的widgetWidget build() => (widget as StatelessWidget).build(this);@overridevoid update(StatelessWidget newWidget) {super.update(newWidget);assert(widget == newWidget);_dirty = true;rebuild();}
}

上面1-9步,看起来比较复杂,其实就是:

mount方法 -> firstBuild -> rebuild -> performBuild -> build -> _widget的build

这里的_widget就是创建element的时候传进来的widget。

我们都知道build方法有个参数build(Build Context context),所以这个context其实就是element,这个context最主要的作用就是告诉我们构建的element在树里面的哪个位置,之后可以沿着树去查找一些信息。

如果是statefulWidget,它里面的build方法如下:

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

我们发现,它之后调用了state.build(this),而不是 (widget as StatelessWidget).build(this);

下面我们看看SingleChildRenderObjectElement的mount方法的调用过程。

在调用mount方法时,会同时使用Widget来创建RenderObject,并且保持对RenderObject的引用,创建完RenderObject之后再把RenderObject挂载到RenderObjectTree树的某个位置

  @overridevoid mount(Element parent, dynamic newSlot) {super.mount(parent, newSlot);// 就是这行代码,创建RenderObject_renderObject = widget.createRenderObject(this);assert(() {_debugUpdateRenderObjectOwner();return true;}());assert(_slot == newSlot);attachRenderObject(newSlot);_dirty = false;}

下面说一下StatefulElement,它是继承于ComponentElement的,所以ComponentElement有的方法,它都有 

  StatefulElement(StatefulWidget widget)// 1. 就是这里,调用了createState: _state = widget.createState(),super(widget) {assert(() {if (!state._debugTypesAreRight(widget)) {throw FlutterError.fromParts(<DiagnosticsNode>[ErrorSummary('StatefulWidget.createState must return a subtype of State<${widget.runtimeType}>'),ErrorDescription('The createState function for ${widget.runtimeType} returned a state ''of type ${state.runtimeType}, which is not a subtype of ''State<${widget.runtimeType}>, violating the contract for createState.',),]);}return true;}());assert(state._element == null);state._element = this;assert(state._widget == null,'The createState function for $widget returned an old or invalid state ''instance: ${state._widget}, which is not null, violating the contract ''for createState.',);// 2. 然后将widget赋值给state里面的_widgetstate._widget = widget;assert(state._debugLifecycleState == _StateLifecycle.created);}

上面主要做了两件事

  1. StatefulElement的构造器中调用了widget.createState()方法
  2. 将widget赋值给state里面的_widget,正是因为这样,我们在state里面才可以通过this.widget拿到对应的widget

总结:

  1. widget创建完之后,Flutter框架一定会根据widget创建一个element,创建完之后会调用element的mount方法,最后根据一系列的调用会调用widget的build(Build Context context)方法。
  2. 如果是renderElement,那么它的mount主要做的就是创建一个_renderObject
  3. 如果是StatefulElement,那么会调用调用了createState,然后将widget赋值给state里面的_widget

2.4. build的context是什么

在StatelessElement中,我们发现是将this传入,所以本质上BuildContext就是当前的Element。

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

我们来看一下继承关系图:

  • Element是实现了BuildContext类(隐式接口)
  • abstract class Element extends DiagnosticableTree implements BuildContext

在StatefulElement中,build方法也是类似,调用state的build方式时,传入的是this。

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

 小结:Element是真正保存树结构的对象:

  • 创建出来后会由framework调用mount方法;
  • 在mount方法中会调用widget的createRenderObject对象;
  • 并且Element对widget和RenderObject都有引用;

4.1 构建widget树:首先,你的应用程序会构建一个由widget组成的树。这些widget描述了应用程序的用户界面。

4.2 构建element树:然后,每个widget会被转换成一个element。这些element也构成了一棵树。

4.3 调用renderObject:Flutter会为每个需要显示的element创建一个对应的RenderObject。这些RenderObject会构成一个树,它用于实际进行渲染。

RenderObject是真正渲染的对象:

  • 其中有markNeedsLayout performLayout markNeedsPaint paint等方法

4.4 布局(layout):然后,Flutter会遍历RenderObject树,计算每个节点的位置和大小。

4.5 绘制(paint):接下来,Flutter会再次遍历RenderObject树,调用每个节点的paint方法进行绘制。

4.6 GPU加速渲染:最后,将绘制指令发送给GPU,最终显示在屏幕上。

相关文章:

flutter 专题四 Flutter渲染流程

一、 Widget - Element - RenderObject关系 二、 Widget 、Element 、RenderObject 分别表示什么 2.1 Widget Widget描述和配置子树的样子 Widget就是一个个描述文件&#xff0c;这些描述文件在我们进行状态改变时会不断的build。但是对于渲染对象来说&#xff0c;只会使用最…...

刘艳兵-DBA028-您可以在 ORCL1 和 ORCL2 数据库都运行其实例的主机上安装“独立服务器的 Oracle 网格基础结构“。哪两个陈述是正确的?

您可以在 ORCL1 和 ORCL2 数据库都运行其实例的主机上安装"独立服务器的 Oracle 网格基础结构"。哪两个陈述是正确的&#xff1f;&#xff08;选择两个&#xff09; A 在完成“用于独立服务器的Oracle Grid Infrastructure”安装后&#xff0c;必须使用crsctl sta…...

前端三件套-css

一、元素选择器 元素选择器&#xff1a;利用标签名称。p,h1-h6...... 行内样式&#xff08;内联样式&#xff09;&#xff1a;例如<p style"color:red;font-size:50px"> id选择器&#xff1a;针对某一个特定的标签来使用。以#定义。 class&#xff08;类&a…...

实验(未完成)

一、拓扑图 二、需求及分析 1、需求 按照图示的VLAN及IP地址需求&#xff0c;完成相关配置。 要求SW1为VLAN 2/3的主根及主网关&#xff0c;SW2为VLAN 20/30的主根及主网关。 SW1和SW2互为备份。 可以使用super vlan。 上层通过静态路由协议完成数据通信过程。 AR1为企…...

Python基础学习_01

目录 1、注释 2、数字和数学计算 3、变量 4、字符串 5、打印 6、本节总结 1、注释 • 什么是注释&#xff1f; 1&#xff09;注释就是用自然语言向代码阅读者说明代码的功能和意义 • 注释 1&#xff09;单行注释使用 # 为开头&#xff1b;并且不能换行…...

鸿萌数据迁移服务: 企业服务器整机在线热迁移, 实现不停机业务转移

天津鸿萌科贸发展有限公司从事数据安全服务二十余年&#xff0c;致力于为各领域客户提供专业的数据存储、数据恢复、数据备份、数据迁移等解决方案与服务&#xff0c;并针对企业面临的数据安全风险&#xff0c;提供专业的相关数据安全培训。 鸿萌数据迁移业务为众多企业顺利高效…...

【C】无类型指针及函数指针

一、无类型指针 &#xff08;1&#xff09;无类指针只包含内存地址&#xff0c;不知道内存地址从存放数据是什么类型&#xff1a; void *ptrNULL; &#xff08;2&#xff09;可以其他类型赋给无类型指针&#xff0c;但是无类型指针赋给有类型指针会警号&#xff1b; …...

VR的左右眼渲染方法

VR的左右眼视频渲染shader unity_StereoEyeIndex 结点可以判断当前渲染的时候左眼还是右眼&#xff0c;所以可以通过着色器来更根据当前眼睛使用不同的渲染方式达到左右眼渲染不同。 Shader "Unlit/VRVideoPlay" {Properties{_MainTex ("Texture", 2D) …...

爬虫-------字体反爬

目录 一、了解什么是字体加密 二. 定位字体位置 三. python处理字体 1. 工具库 2. 字体读取 3. 处理字体 案例1&#xff1a;起点 案例2&#xff1a;字符偏移&#xff1a; 5请求数据 - 发现偏移量 5.4 多套字体替换 套用模板 版本1 版本2 四.项目实战 1. 采集目…...

vue2组件封装和UI组件的二次封装,方法,属性,ref的传递

封装组件使用v-model 使用方法props接受value值&#xff0c;当值发生变化的时候再通过this.$emit("input", newValue)&#xff0c;则实现了简单组件的v-model封装,如果不使用第三方UI可以接受到的值使用watch或者计算属性保存&#xff0c;然后再通过事件派发自己保存…...

喜报!景联文科技成功通过DCMM数据管理能力成熟度二级认证

10月30日&#xff0c;中国电子信息行业联合会公示了新一批DCMM贯标企业&#xff0c;景联文科技成功通过DCMM数据管理能力成熟度二级认证&#xff08;乙方认证&#xff09;。 DCMM是《数据管理能力成熟度评估模型》的简称&#xff0c;是我国在数据管理领域首个正式发布的国家标准…...

从壹开始解读Yolov11【源码研读系列】——Data.dataset.py:模型训练数据预处理/YOLO官方数据集类——YOLODataset

【前情回顾】在上一篇文章记录了YOLO源码data目录下的 base.py 文件&#xff0c;其中定义了一个可灵活修改的数据加载处理基类——Class BaseDataset 灵活基类博文地址&#xff1a;https://blog.csdn.net/qq_58718853/article/details/143249295 【实验代码】所有实验代码上传至…...

C语言初阶必会的练习题(3)之位操作符(^ 、、>>等)的应用

C语言初阶必会的练习题&#xff08;3&#xff09; 放在最前面的1、不允许创建临时变量&#xff0c;交换两个整数的内容1.1、分析&#xff1a;见代码注释&#xff08;a&#xff09;方法 1&#xff08;b&#xff09;方法 2 1.2、结果展示方法 1 的 结果&#xff1a;方法 2 的 结果…...

MongoDB面试专题33道解析

大家好&#xff0c;我是 V 哥。今天给大家分享 MongoDB的道 V 哥原创的面试题&#xff0c;收藏起来&#xff0c;一定会对你有帮助。 V 哥推荐&#xff1a;2024 最适合入门的 JAVA 课程 1. 你说的 NoSQL 数据库是什么意思&#xff1f;NoSQL 与 RDBMS 直接有什么区别&#xff1f…...

Laravel 安全实践:如何防止 XSS 攻击

在当今的网络环境中&#xff0c;应用程序的安全性越来越受到开发者和企业的重视。跨站脚本攻击&#xff08;XSS&#xff09;是常见的网络安全威胁之一&#xff0c;它通过在目标网站上注入恶意脚本&#xff0c;窃取用户信息或执行恶意操作。作为流行的 PHP 框架&#xff0c;Lara…...

《Java Web 开发》

一、引言 在当今数字化时代&#xff0c;Web 应用程序已经成为人们生活和工作中不可或缺的一部分。Java Web 开发作为一种广泛应用的技术&#xff0c;以其强大的功能、稳定性和可扩展性&#xff0c;在企业级应用开发中占据着重要地位。本文将深入探讨 Java Web 开发的各个方面&a…...

Vector和ArrayList

Vector和ArrayList都是Java集合框架中的动态数组实现类&#xff0c;它们之间存在一些显著的区别。以下是对Vector和ArrayList的详细比较&#xff1a; 一、线程安全性 Vector&#xff1a;是线程安全的&#xff0c;即多线程情况下&#xff0c;Vector可以保证容器的同步性。Vect…...

关于我、重生到500年前凭借C语言改变世界科技vlog.16——万字详解指针概念及技巧

文章目录 1. sizeof 和 strlen1.1 sizeof1.2 strlen 2. 数组和指针结合的试题深入解析2.1 一维数组2.2 字符数组代码1代码2代码3代码4代码5代码6 2.3 二维数组 3.指针运算的试题深入解析题1题2题3题4题5题6题7 希望读者们多多三连支持小编会继续更新你们的鼓励就是我前进的动力…...

开发更便利!迅为RK3568/RK3588 定制分区镜像发布

目前迅为所维护的Linux SDK一直延续RK官方默认分区结构&#xff0c;而迅为另维护了的一套定制分区结构的SDK&#xff0c;两种不同的分区结构都有着各自的特性&#xff0c;RK默认分区镜像和定制分区镜像对比如下所示&#xff1a; rk传统分区适合启动速度要求高且硬件配置固定的系…...

基于Springboot的学生宿舍管理系统的设计与实现-计算机毕设 附源码 26991

基于Springboot的学生宿舍管理系统的设计与实现 摘 要 学生宿舍管理系统在高校管理中具有重要的作用&#xff0c;为提高宿舍管理效率和服务质量&#xff0c;本文基于Springboot框架开发了一款学生宿舍管理系统。该系统主要分为管理员、学生用户和宿管用户三类角色&#xff0c;每…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

Debian系统简介

目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版&#xff…...

循环冗余码校验CRC码 算法步骤+详细实例计算

通信过程&#xff1a;&#xff08;白话解释&#xff09; 我们将原始待发送的消息称为 M M M&#xff0c;依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)&#xff08;意思就是 G &#xff08; x ) G&#xff08;x) G&#xff08;x) 是已知的&#xff09;&#xff0…...

【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)

要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况&#xff0c;可以通过以下几种方式模拟或触发&#xff1a; 1. 增加CPU负载 运行大量计算密集型任务&#xff0c;例如&#xff1a; 使用多线程循环执行复杂计算&#xff08;如数学运算、加密解密等&#xff09;。运行图…...

LLM基础1_语言模型如何处理文本

基于GitHub项目&#xff1a;https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken&#xff1a;OpenAI开发的专业"分词器" torch&#xff1a;Facebook开发的强力计算引擎&#xff0c;相当于超级计算器 理解词嵌入&#xff1a;给词语画"…...

css3笔记 (1) 自用

outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size&#xff1a;0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格&#xff…...

【生成模型】视频生成论文调研

工作清单 上游应用方向&#xff1a;控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...

JavaScript基础-API 和 Web API

在学习JavaScript的过程中&#xff0c;理解API&#xff08;应用程序接口&#xff09;和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能&#xff0c;使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

Python 实现 Web 静态服务器(HTTP 协议)

目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1&#xff09;下载安装包2&#xff09;配置环境变量3&#xff09;安装镜像4&#xff09;node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1&#xff09;使用 http-server2&#xff09;详解 …...