• duilib框架分析(一)概述


    duilib概述

    参照duilib类图
    这篇主要记录框架思路

    PlaceHolder

    PlaceHolder字面上看是布局容器,单独将布局相关的操作分离出来。从抽象来看,可以理解为就是一个矩形块。最重要的属性为:

    • m_rcItem 矩形坐标范围
    • m_cxyFixed 固定宽高

    m_cxyFixed

    虽然字面上表示固定宽高,但实际有三种情形,固定宽高,auto,stretch

    • 固定宽高,就是显式的指定 比如<Control width=”50” height=”20”..>
    • auto 宏定义 DUI_LENGTH_AUTO -2 比如<Control width=”auto” height=”20” ../>
    • stretch 宏定义 DUI_LENGTH_STRETCH -1 比如中<Label width=”stretch” .../>

    这说明m_cxyFixed的可能取值有下面几种情形:{50,20};{-1,-1},{-2,-2},或相互之间组合的情形

    除此之外,在PlaceHolder的构造中,可以得到一个信息
    控件默认是DUI_LENGTH_STRETCH,也就是默认拉伸,这一点比较重要

    • 最大尺寸{9999999,9999999}
    • 最小尺寸{-1,-1}

    SetPos()

    从类图中可以看到,PlaceHolder的SetPos()是最原始操作,仅仅做了简单赋值。对于后面的派生类来说,显然是不够的。因此做为虚方法,派生类会重载

    Control

    Control是真正意义上的控件基类,占据了95%的属性和方法,也是内容最庞大的类。除了从PlaceHolder继承了一些布局属性方法,它还有背景色,边框,鼠标,键盘,事件等等各种属性和方法。

    下面挑几个比较常用又比较重要的属性记录

    SetAttribute:此方法为虚方法,用于给某类控件设置属性。由于派生类的控件可能具有自己独特的属性,因此,只要某个控件有专项属性,SetAttribute()方法就会重写。在类图中很清晰的表明这一点
    SetPos():Control已经不同于最原始的PlaceHolder,进化的比较高级。因此SetPos()方法也要重写。在这个地方穿插一段duilib的思路。

    ————穿插的一段内容————

    在duilib中,可以简单理解为只有两种控件

    一种是容器,也就是Box及其以上派生类。
    容器的特点:主要用于布局,可以包含容器和控件
    容器也是控件

    另一种是控件,这里是狭隘的说法,控件就是真正工作的控件,比如Control Label Button CheckBox等等,它们是叶子结点,不能再包含容器和控件

    仅仅为了方便理解做了这样的区分,实际上容器也是控件
    有了这个认识,再来看Control的SetPos()就很容易理解,Control由于是叶子结点,不再包含其它,因此它的SetPos()就是将自己移一移就可以,主要工作就是和祖先做交集运算而不致于超出祖先窗口之外,和子窗口的原理相似

    Box

    Box继承自Control,是容器的基类。由于它能包含子控件,因此多了一些Add,Remove的方法。Box承前启后,属于非常核心的类。

    下面记录它的SetPos()

    SetPos()

    Box是容器,和Control进行比较,它的责任明显要重大很多。包含两层含义

    1. 首先Box也是控件,因此在进行SetPos()的时候,需要调用Control::SetPos()将自己固定好,也就是说先把自己安排好
    2. 之后,需要将自己的子控件也进行布局,这就是容器责任重大的原因,不仅要处理自身,还要附带将子控件一并进行布局。因此Box的SetPos()又复杂了一点。

    下面看下Box的布局特点

    Box的布局比较简单,就是无序叠放。如上图所示,Box包含三个子控件,每个控件默认都以左上角原点为起点进行堆放

    1.如果一个Box是auto,它怎么计算自己需要的尺寸呢?从上面自然可以想到,它会遍历子结点,取最大的宽,最大的高做为自己的尺寸,也就是最大可能的容纳所有子控件。SetPos()的算法确实也是这么做的。

    如果想在Box容器中有序布局,只有采用Float(浮动)的方式。Float和CSS中的浮动很类似,就是采用绝对定位,不再受限于父结点。 

    SetPos()的具体调用为:SetPos() -> ArrangeChild() -> SetFloatPos() ->EstimateSize()

    其中EstimateSize后面专门记录

    有了思路再看上述的代码就比较简易

    估算 EstimateSize

    EstimateSize()是在PlaceHolder中定义的虚方法,字面上看是估算、预估等意思,这里采用估算的说法。在PlaceHolder中仅仅做了原始的赋值操作。
    为什么需要估算呢?仅仅因为auto关键字。auto的意思是自适应,因为不能从字面上得到确切的尺寸信息,因此需要估算。

    举个例子说,有一Box包含三个控件A B C,在进行SetPos()的时候必须知道A B C究竟多大,不然没法对它们进行排列布局

    <Box width=”200” height=”auto”>

    <Control...>

    <Control..>

    ..</Box>

    控件的估算

    控件不能再包含控件和容器,它的估算方式进入源码可以得到下面信息:

    1. 如果有明确的高,或者拉伸,就返回值
    2. 如果宽高某项为auto就要估算。这也验证前面所说的,凡是需要Estimate的,都是和auto有关联

    由于Control形式简单,它的估算算法是,先看有没有图片,像背景图和状态图等,如果有,就以背景图做为自己的尺寸,接着再看有没有文本之类,由于Control不具备文本的能力,因此在Label及其以上的派生类中会重写。

    里面涉及到GDI+的操作,估算一幅图片或一片文字的尺寸,GDI+完全有能力做这事儿

    总结:Control的估算,是以自己内部的图和文本为基准的相关计算,它的源码写的比较容易理解,可以参看源码Control::EstimateSize()

    有了这个理解,就可以明白下面这句:

    <Control width=”auto” height=”auto” bkimage=”file=’...png’” />

    Control继承自PlaceHolder,它的默认m_cxyFixed没有改写,即默认拉伸。上面指明auto之后,所占用(估算)的面积以背景图片尺寸为准,可以理解为Control自适应图片

    容器的估算

    由于容器的形式有很多种,比如Box,VBox,HBox,每种的规则都不同,因此估算的方法也不同,所以EstimateSize()就设为虚方法

    下面先不谈具体的算法,而是拿容器和控件进行对比。

    容器可以包含容器,也可以包含控件。如果对容器进行估算,就是对子控件的遍历估算,如果它是容器,就进一步的遍历,直到穷尽到叶子结点(控件)

    这样来看,最终还是控件的估算,理解这一点不至于在复杂的布局中绕迷糊

    Box的估算

    具体算法在源码中写的比较清楚,一句话概括:遍历子控件,取最大的宽度,和最大的高度做为自己的尺寸

    下面举个简例:

    <Box width=”200” height=”auto”>

    <Control name=”A” bkcolor=”gray” />

    <Control name=”B” height=”30” bkcolor=”red” />

    <Control name=”C” width=”auto” height=”auto” bkimage=”file=’abc.png’ “ />

    </Box>

    分析:Box如果想知道自己尺寸多大,就必须先估算自己的子控件大小。

    控件A,默认是DUI_LENGTH_STRETCH -1 拉伸,估算结果为:{-1,-1}
    控件B,width默认为-1 拉伸,估算结果为:{-1,30}
    控件C,由于设置了auto,估算结果为图片尺寸,假如为{140,140}

    通过大小比较,得到height=140,最后的Box={200,140}

    程序运行结果如下:

    完整的xml:

    <?xml version="1.0" encoding="UTF-8"?>

    <Window size="600,500" caption="0,0,0,35" shadowattached="false">

    <Box width="200" height="auto" name=”root”>

    <Control name="A" bkcolor="gray" />

    <Control name="B" height="30" bkcolor="red" />

    <Control name="C" width="auto" height="auto" bkimage="file='abc.png'" />

    </Box>

    </Window>

    布局起源

    这节从0开始记录,为什么上面的XML会显示这样的结果?

    为了简化问题,将窗口的默认阴影去掉

    通过框架源码可知,Window的成员m_pRoot就是指向上面name=”root”的Box *

    Window::Paint()中的估算

    代码运行到Window::Paint()方法时

    由于m_pRoot的height为auto,此处进行了估算操作,同时给出了预定义的最大尺寸为{99999,99999}

    接下来,m_pRoot执行EstimateSize()进行估算,前面已经说明思路,流程比较简单,最终得到的估算结果为{200,140}

    最后,调用::MoveWindow将窗口进行调整,而尺寸是估算的结果,这说明一个问题:

    虽然<Window>设置了size=”600,500”,但是这个属性对最终窗口尺寸的影响不是绝对的,甚至不起作用。从这个例子可以看到,顶层Box设置了auto,在估算的情况下以估算结果做为最终结果

    因此,可以将上面的XML改为

    <Window>

    <Box width=”200” height=”auto”..>

    ...

    不再设置Window的size,而是将Box的height设为auto,这种情形就是窗口自适应。在自适应情况下,框架给了一块最大的可用面积{99999,99999},在这块面积上进行估算

    此外,从上面的分析中还可以得到一些简单的结论

    1、如果<Window>不设置size,那么最外层容器要么是给定固定宽高,比如<Box width=”200” height=”100”>,要么设置auto。
    如果外层窗口什么也不设置,这个窗口就是不存在尺寸。因为默认情况下,容器的m_cxyFixed是stretch,结果什么都没做。

    <Window> //这种写法是错误的

    <Box>

    ..

    ...</Box>

    </Window>

    Window::Paint()中的SetPos()

    接着代码运行到

     

    这个地方是布局的开始,此处的rcClient有两种来源

    1. 如果m_pRoot什么都没设置,那么<Window>必须给出一个size,比如:
      <Window size=”600,500”>

    <Box>

    ...

    此时,m_pRoot自然也不需要估算,那么rcClient就是size的尺寸。为什么它们之间是相同的呢?
    这个问题需要回到<Window>标签的解析,对于<Window>的size属性,在WindowBuilder::Create()中可以看到

    此处调用了Window的SetInitSize()方法,在此方法中调用了API ::SetWindowPos(),此处解释了上面的问题

    1. 如果m_pRoot设置了auto,就通过估算得到窗口的尺寸

    不管如何,rcClient都是通过::GetClientRect()得到..

    另外,如果两者都设置了固定宽高,那么以window的size为准,多出的被裁剪掉,在这种情况下,size起了作用

    BoxSetPos()

    m_pRoot进行SetPos(),这个问题实际上可以换个说法,就是对容器进行SetPos()。因为最外层的控件必须是容器 — Box及其以上派生类。

    但不管如何,此时rcClient必须有值,也就是说SetPos之前坐标事实上已经确定好了。

    对于本例来说,m_pRoot是因为估算了自己确定了Window的尺寸。对于容器的SetPos()前面着重写过,需要两步,1. 先将自己固定好 2.再将子控件进行排列

    对于第一步,就导致m_pRoot和窗口完全一样大小。

    第二步的调用顺序:m_pRoot->SetPos()-> [m_pLayout->ArrangeChild()] -> [m_pLayout->SetFloatPos()] -> pControl->SetPos()

    在这段流程的最后,每个控件的m_rcItem得到了值,确定了自己在容器中的位置,这个解释了每个控件的位置究竟从哪来的问题。

    由于最外层必须是容器,因此也可以说所有控件都在容器中

    VBoxHBox

    两者非常相似,这里选VBox进行记录

    Layout

    对于容器,另外又分离出一个类Layout,专门用了布局相关的操作。从源码中可以看到,Layout主要处理布局相关的参数,比如padding,margin,ArrangeChild,AjustSizeBychild等

    由于容器也有区别,因此又从Layout派生出VLayout,HLayout,分别对应于VBox,HBox

    VBox的构造

    VBox比较简易,构造方法:VBox::VBox() : Box(new VLayout())

    仅仅产生一个VLayout,除此外再也没有其它

    VLayout重写了ArrangeChild,AjustSizeByChild这两个最重要的布局方法,原因在于VBox有自己的一套规则

    VBox是垂直容器,它的子控件默认是从上往下排列,特别着重y轴。如上图所示,假如VBox的高100,它的算法思路是:

    高100称为可用高度

    如果子控件的高度为固定高,就减

    如果子控件的高度为stretch,就设置一个参数nAdjustables进行计数,这个词可以理解为自适应,也就是当前不知道它有多高,但通过最后的计算能得到调整。
    如上图所示,假如A是固定高20,B C未设值,就是默认stretch,则nAdjustables为2,首先将明确具有固定高的20减去,然后将剩余的可用高/nAdjustables,得到B C的高度为40,40。
    从这个算法中可以看到,如果height设置为stretch,就默认平分剩余的可用高。

    除此外,还掺杂着其它的附加属性计算,比如padding,margin,ChildMargin等等,算法原理比较简单

    VBoxEstimateSize/AjustSizeChild

    EstimateSize在Box中被重载,核心是调用m_pLayout->AjustSizeByChild(items,szAvailable)
    由于VBox没有重写任何方法,因此EstimateSize调用的是VLayout::AjustSizeChild()

    在这个方法中,关键的就是一句:

    totalSize.cy += itemSize.cy + pChildControl->GetMargin().top + pChildControl->GetMargin().bottom
    totalSize将每个子控件的高度相加,也说明了VBox的特点,就是上下堆叠

    总结

    1、如果Window没有设置size,则顶层容器至少有auto

    2、如果Window设置了size

    1) 顶层容器设置了auto,则需要估算,以估算结果为准

    2) 顶层容器也设置了固定宽高,则以size为准

    <Window>

    <Box width=”200” height=”auto”>

    <Control height=”30”..>

    <VBox height=”auto”..>

    <Control height=”50”..

    ..

    ...

    分析:Window没有设置size,则顶层容器必须有一个auto

    1)首先进行Box的EstimateSize()。调用m_pLayout->ArrangeChild(),在排列子控件时,再对子控件进行EstimateSize(),即Control的估算,和VBox的估算。

    Control的估算比较简单,牵涉到图片和文本

    VBox估算则调用VLayout的AjustSizeByChild,主要原理是将控件高度相加

    如果VBox有嵌套,则递归进行估算,以此类推

     

     

  • 相关阅读:
    android使用ant编译打包
    Android OpenGL ES 2.0 (二) 画立方体
    Android OpenGL ES 2.0 (三) 灯光pervertex lighting
    OpenGL ES2.0里的3种变量
    JAVA对DOM的一些解析、修改、新增操作
    webservice(二)示例代码
    linux改IP
    android从未安装的apk文件里获取信息(包信息,资源信息)
    Android OpenGL ES 2.0 (一) 画三角形
    一个关于closure的问题.
  • 原文地址:https://www.cnblogs.com/tinaluo/p/14394720.html
Copyright © 2020-2023  润新知