egret-docs-master/extension/EUI/autoLayout/FailureToVerify/README.md

89 lines
5.0 KiB
Markdown
Raw Normal View History

2024-06-19 13:32:32 +08:00
这节内容对于学习使用EUI里的组件并不是必须的但是了解失效验证机制与自动布局的关系有助于理解自动布局的过程从而在遇到布局问题时能够加快调试速度。
## 什么是失效验证
失效验证是一种延迟应用改变的优化措施。举个例子我们现在有一个UI组件需要在`width`或`height`发生改变后,根据当前尺寸用`Graphics`去重绘一个矩形背景。在立即应用的情况下,当我们分别修改它的`width`和`height`时组件会分别执行2次Graphics绘制。如果我们在一个for循环里不断改变尺寸改变多少次就立即绘制多少次。而在失效验证的情况下当尺寸发生改变时它只是用一个变量标记下尺寸发生了改变然后延迟一帧检查这个变量再统一执行一次`Graphics`绘制。所以无论你同一时间改变了尺寸多少次,结果都只绘制了一次。
以下代码就是失效验证机制的最简单实现:
~~~ typescript
class UIComponent extends egret.Shape{
public constructor(){
super();
}
private sizeChanged:boolean = false;
private _width:number;
public get width():number{
return this._width;
}
public set width(value:number){
if(this._width==value)
return;
this._width = value;
this.sizeChanged = true;
this.invalidateProperties();
}
private _height:number;
public get height():number{
return this._height;
}
public set height(value:number){
if(this._height==value)
return;
this._height = value;
this.sizeChanged = true;
this.invalidateProperties();
}
public commitProperties():void{
if(this.sizeChanged){
this.sizeChanged = false;
this.redrawBackground();
}
}
private redrawBackground():void{
var g:egret.Graphics = this.graphics;
g.clear();
g.fillStyle = 0x009aff;
g.fillRect(0, 0, this._width, this._height);
}
private invalidatePropertiesFlag:boolean = false;
public invalidateProperties():void{
if(this.invalidatePropertiesFlag)
return;
this.invalidatePropertiesFlag = true;
this.addEventListener(egret.Event.ENTER_FRAME,this.validateProperties,this);
}
private validateProperties(event:egret.Event):void{
this.removeListener(egret.Event.ENTER_FRAME,this.validateProperties,this);
this.commitProperties();
this.invalidatePropertiesFlag = false;
}
}
~~~
`redrawBackground()`就是根据当前宽高绘制背景的方法。当`UIComponent`的`width`或`height`在外部被赋值修改后,不直接调用`redrawBackground()`重绘背景,而是先用`sizeChanged`属性标记尺寸发生了改变,然后调用`invalidateProperties()`方法,标记有属性失效,需要延迟验证。`invalidateProperties()`方法里通过监听`ENTER_FRAME`事件,来实现在下一帧再执行`commitProperties()`方法。`commitProperties()`方法里就是判断`sizeChanged`属性,然后最终执行`redrawBackground()`;在这之前,无论你对`width`或`height`执行了多少次修改,最终`redrawBackground()`只会执行一次。在实际使用中,这些异步过程都封装好了,开发者只需要关注那一对方法: `invalidateProperties()``commitProperties()` ,前者标记属性失效,后者应用失效的属性。
## 针对布局的失效验证
上文我们介绍了一种通用的属性失效验证方法,作用是能够有效避免不必要的重复计算,从而显著提升运行时计算性能。那么我们是如何把这个优化机制应用到自动布局中去的呢?
EUI中除了通用的属性失效验证还封装了针对布局的两种失效验证一个是测量验证一个是布局验证。写法与 `invalidateProperties()``commitProperties()` 如出一辙,这里就不贴代码了。同理,我们也只需关心那一对方法,测量:`invalidateSize()` 和 `measure()`,前者标记尺寸失效,后者测量尺寸,布局:`invalidateDisplayList()` 和 `updateDisplayList()`前者标记布局失效后者更新布局。EUI内维护了三个失效列表然后会按照:提交属性->测量尺寸->更新布局这种顺序依次进行验证。另外单个失效列表内也是有序的按照UIComponent在显示列表内的嵌套层级排序。提交属性阶段是由外至内测量阶段是由内至外更新布局阶段又是由外至内。如下图
![](55cdd5b0ed17f.jpg)
这种排序方式主要是为了减少重复的计算。例如测量阶段,父级的测量结果是由子项决定的,显然由内至外的测量顺序才能保证测量结果的正确性。若是无序的,子项测量的尺寸发生改变后,会通知父级再次测量。就会造成大量重复的计算。这里面其实只有测量和布局阶段与嵌套层级执行顺序有关,提交属性阶段只是组件内部的失效验证,无所谓由内至外或由外至内。