大多数刚使用duilib的朋友时候非常依赖duilib自带的设计器,用他可以拖拉控件,可视化的做出自己想要的界面。可是用一段时间就会发现原带的设计器有很多bug,时不时会崩溃,支持的控件数量有限,属性数量也有限,导出的代码冗余。当时问了几个高手,大家建议不要使用设计器而应该自己手写xml代码。起初手写时感觉特别麻烦,可是用几天后你会发现手写要比使用设计器好得多:你可以更加了解duilib,熟悉每个控件的各个属性,对控件的控制也更加方便。而如果想称心如意的脱离设计器去编写xml文件,有非常有必要弄明白各个布局的用法和布局技巧。完全可以靠手写xml来做出一个程序的界面,相信用了一段duilib的朋友也是这样。
在这里提醒一下新手朋友,在duilib的根目录有一个属性列表.xml的文件,他包含了绝大多数控件的绝大多数属性的介绍,有不懂的属性记得时常翻看他,同时不得不说这个文件包含的属性的确是不全面的,想要知道最全面的属性信息,可以看每个控件的源代码,在SetAttribute函数中可以看到最全面的属性信息!
要想手写xml,当然必须有一个编写工具,原则上只要是可以编写文本的工具都可以,大家根据习惯自己挑选适合的工具,我目前在使用的是sublime这款工具,感觉编写xml非常方便,使用界面也不错。
6大布局的作用:
duilib的Layout目录专门放置布局相关的容器控件,这6个布局分别为:Container、VerticalLayout、HorizaontalLayout、TileLayout、TabLayout、ChildLayout。容器之间可以任意相互嵌套,我分别说明他们的用法。
首先我要说明一下,下面介绍的时候,我都默认认为每个控件的float属性为false,也就是不使用绝对定位,这个属性会打破各个布局的作用。
Container:
Container布局是其他所有布局以及含有容器特性(如CList、CListContainerElement)的控件的基类,而实际上开发过程中很少使用这个布局,只用他来做其他更高级的布局的基类。因为Container布局中的所有控件都会自动填充满整个布局,所有的控件都叠到了一起,假如恰好有什么需求要让子控件都覆盖起来,而且可以随着容器的改变而自适应填充的话,Container就是不二之选。而除了这个效果之外,一般我们不使用它。
VerticalLayout、HorizaontalLayout:
VerticalLayout与HorizaontalLayout布局无疑是duilib中最常使用的两个布局,巧妙的使用这两个布局可以满足大多数的布局需求。从单词的意思上不难看出VerticalLayout是纵向布局,HorizaontalLayout是横向布局。这门两个直接继承自Container布局。
VerticalLayout布局会让他包含的元素都纵向排列开,HorizaontalLayout布局会让给他包含的元素都横向排列开:如图
我故意没让控件填满整个容器,为了说明这两个布局不会强行让子元素的总和去填满容器,纵向布局会从上到下根据每个控件的高度让他们排到一起,横向布局会从左到右根据每个控件的宽度让他们排到一起。另外可以看到,纵向布局只关心子元素的高度,而不会强行让子元素的宽度等于容器的宽度,这点从图片可以看到,横向布局同理也是只关心子元素的宽度。而这两个布局经常会嵌套使用,如下效果:
可以看到我最外层使用了一个纵向布局,他包含了横纵横三个布局(分别为红绿蓝颜色),每个横向布局里又分别包含了几个按钮。我们在编写界面时经常用到这个方法!以下是这个布局效果图对应的xml代码:
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<Window size="300,226">
<VerticalLayout width="300" height="318" bkcolor="#FFFFFFFF">
<HorizontalLayout width="300" height="65" bkcolor="#FFFF0000">
<Button text="按钮测试" width="60" height="35" />
<Button text="按钮测试" width="60" height="63" />
<Button text="按钮测试" width="60" height="35" />
</HorizontalLayout>
<VerticalLayout width="300" height="96" bkcolor="#FF00FF00">
<Button text="按钮测试" width="60" height="64" />
<Button text="按钮测试" width="60" height="75" />
<Button text="按钮测试" width="60" height="64" />
</VerticalLayout>
<HorizontalLayout width="300" height="65" bkcolor="#FF0000FF">
<Button text="按钮测试" width="60" height="47" />
<Button text="按钮测试" width="60" height="64" />
<Button text="按钮测试" width="60" height="64" />
</HorizontalLayout>
</VerticalLayout>
</Window>
TileLayout:
TileLayout布局是用来做类似360工具箱的效果:
在前面的文章里,我写的《duilib中ListCtrl控件的实现》和《仿酷狗音乐播放器开发日志十三——左侧功能块的完善》正是使用了这个布局完成的。这个布局有有两个关键属性:
<Attribute name="columns" default="1" type="INT" comment="列数,如(4)"/>
<Attribute name="itemsize" default="0,0" type="SIZE" comment="子项固定大小,如(128,128)"/>
columns和itemsize属性,这两个属性不能一起用,应该只是用其中的一个。使用columns属性可以来设置每行中包含的列数,他会自动把包含的元素从左到右从上到下按照columns属性的设置排列起来,他把每行的列数固定死了。而itemsize有两个字段,通过我读源码,发现第二个字段是无效的,我们只要使用第一个字段就行了,他会设置每个元素所占的区域,比如容器的宽度是500,给itemsize设置为 100 x 10,那个每行就会容纳5个元素,当我们拉伸了窗体让容器宽度变为700,那么每行就会自动容纳7个元素,这意味着使用这个属性会让每行容纳的元素个数是自动可变的!这在很多情况下是很有用的属性。注意itemsize并不是直接设置了子控件的大小,而只是限制了子控件的最大区域。比如itemsize为100x100,而子控件设置为50x50,那么 最终的子控件大小为50x50,而子控件的位置会按照100x100来计算。这个希望读者自己实践一下来理解这个效果!
TabLayout:
TabLayout布局同样常用,他就像MFC的选项卡CTabCtrl控件,如图:
但是在duilib中TabLayout只是下面的布局界面,而不包含顶端的选项卡按钮,所以经常用Option控件配合他一起使用,使用他时他会把他包含的下一级元素作为一个页面,所以我们通常在他里面放入横纵向布局来作为一个页面,在横纵向布局里再规划每个页面的外观。
这个控件的详细使用方法大家可以看duilib自带的360demo,我就不赘述了!
ChildLayout:
ChildLayout布局比较少用,因为他的功能可以用其他布局来代替,他的作用就是从一个xml文件中加载布局来嵌入到ChildLayout布局所在的地方,使用这个布局一般只需要指定xmlfile属性来设置xml文件位置就可以了。他的意义在于可以把繁杂的大量xml代码分隔开。比如他和TabLayout布局结合,让TabLayout布局包含5个ChildLayout布局,而每个ChildLayout布局分别从5个xml文件加载自己的布局文件,这样就可以分块化的编写布局代码。
实际上有个比他更好用的标签,就是Include标签,Include不属于布局,但他的作用在布局方面非常类似ChildLayout,指定他的Source属性到某个xml文件就可以了。相对ChildLayout,Include的优点是可以自动识别自定义控件,而ChildLayout不可以!
在这里要提一下360Demo的自定义控件,这个demo的自定义控件做法误导了很多人,里面使用了自定义控件的方法,把一个xml布局文件嵌入到界面里,这种做法完全没必要,直接使用Include标签,一句xml代码可以完全代替自定义控件。
这里给出一个Include的用法:
<Include source="Login\ScrollBar.xml" count="1" />
其中source属性指定xml文件的路径,count指定解析的次数。
绝对布局的意义与用法:
在知道了6大布局的用法之后,知道了各种样式的界面外观的大致布局方法,而这还远不够让我们写出漂亮的布局外观,只有配合相对布局与绝对布局才可以更好的控制界面的元素。值得一提的是,我这里说的相对布局和绝对布局并不是一个容器或者控件,这只是一种技巧和使用方法,用在容器布局所包含的控件上,常用到横纵向布局中。
我先来介绍绝对布局,笼统上说绝对布局和相对布局其实只有一个差别,也就是我在前面提到的float属性,容器中包含的控件float属性为真就是绝对布局,为假就是相对布局。不要小看这一个属性,他带来的效果可以天壤之别!
给控件的float属性设为真后,就使用了绝对布局,故名意思,绝对布局就是让控件的坐标绝对化,这样这个控件就不受他的容器的束缚而可以自己随意设置自己的位置!比如在横纵向布局中给他们包含的子控件设置float属性,这个控件就不会被自动横纵向排列。而我的建议是,能不用绝对布局就别用绝对布局!原因有三个:
1)绝对布局破坏了各个容器的特性,而不受容器的束缚。
2)绝对布局让控件的坐标固定,不利于控件自动调节位置。
3)后面提到的相对布局几乎可以完成绝对布局的所有特性。
那么为什么要用绝对布局,因为他的一个功能是相对布局无法完成的,就是让控件或者布局重叠或者相交!有的时候我们必须这样做来让控件组合起来达到一些效果。我可以明确的说,我在做仿酷狗播放器的过程中,整个xml布局代码只用了2个绝对布局,一个是编写搜索栏《仿酷狗音乐播放器开发日志二——搜索栏的编写》,一个是编写电台控件。如图:
另外一个非常经典的使用绝对布局的例子就是我前几天写的《用duilib制作仿QQ2013动态背景登录器》,这些例子都是因为要让控件重叠起来组合出新的控件才使用了绝对布局,如果不让控件重叠或者没有特殊需求,最好别用绝对布局。
虽然不建议使用,但我也得说一下绝对布局相关的属性和使用技巧。
1)把float属性设置为真。
2)设置pos属性,这个属性在float为真时才有效,他包含四个字段,分别以为了控件的左上右下下个坐标的位置,但是建议只指定前两个字段来设置控件的左上角的坐标,控件的宽度用width和height属性来控制,这样做的好处是避免了计算右下角坐标的繁琐!以后修改的时候也很清晰!
举出一个例子:
<Label name="MusicName" float="true" pos="100,100" width="26" height="22" textcolor="#FF505050" endellipsis="true" />
这里设置了一个Label控件,左上角放到100,100的坐标上,控件高22,宽26
不得不提一个很有用的属性,那就是relativepos属性,属性列表没有列出这个属性,这是我自己总结的
<Attribute name="relativepos" default="0,0,0,0" type="RECT" comment="设置相对移动,float为真时,分别表示横纵向位移值,横纵向缩放值,移动单位建议50或100"/>
用了这个属性,就可以让控件拥有相对布局的一部分特性,那就是根据容器的大小,自己可以调整位置和大小!这个特点我在《仿酷狗音乐播放器开发日志二——搜索栏的编写》用到了,是为了让搜索按钮可以自己移动。这个属性的前两个字段表示横纵向的位移值,后两个字段表示缩放值,具体效果大家应该自己实践一下!另外这个属性默认是有严重bug的,就是窗体最小化再恢复后有这个属性的控件会自动无规律偏移,这个bug我修复了,详见《仿酷狗音乐播放器开发日志二——搜索栏的编写》。
这样就介绍完了绝对布局,然后就是整片文章的最重要部分,相对布局!
相对布局的意义与用法:
我把相对布局的介绍放到最后,因为它很重要!
在容器内部使用控件或者容器时,float属性设置为flase(duilib默认为false)就是相对布局了。这是我非常推荐使用的,前面我也说了我在写仿酷狗的整个布局中,上百个控件中我只给两个控件使用了绝对布局,其余都是相对布局。他的优点如下:
1)布局和控件是可以根据窗体的大小改变而自动调整位置的,这点很重要
2)不需要绝对布局那样麻烦的计算各个控件的位置
3)在容器中调整前一个控件的位置,后面的控件都会自动调整坐标
其实总得来说使用相对布局意义就是使用布局控件的自动排列特性!
使用了相对布局后,就不用设置float属性和pos属性,一般只设置甚至不设置width和height属性。这点很重要,如果你的控件或者布局的大小是固定的,那么就设置width和height属性,如果想让控件或者布局根据窗体的大小而自动调整大小和位置,就不设置这两个属性。如果只设置了一个属性,比如width设置为100,而height不设置,那么他的高度是自动调整的而宽度是固定的。父容器会自动安排他包含的元素,让含有width和height属性的控件占据相应的大小,把剩下的空间都分配给没有设置wieth和height属性的容器或者控件里。看下面一个例子:
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<Window size="300,300">
<VerticalLayout name="Father">
<HorizontalLayout name="Sub1" height="50" >
</HorizontalLayout> <VerticalLayout name="Sub2">
</VerticalLayout> <HorizontalLayout name="Sub3" height="50" >
</HorizontalLayout>
</VerticalLayout>
</Window>
可以看到窗体的大小为300x300,而最外层的是一个名为Fahter的纵向容器,包含三个子容器。而Father没有指定width和height,所以当我改变了窗体的大小时,Father会自动调整自己的大小到和窗体大小相同,而三个子容器我都眉头指定width属性,所以三个子容器的宽度和Father是一样,也就是他们的宽度都是和窗体宽度一样,并且会自动调整。Sub1和Sub3的高度设置为50,所以他们的高度就固定了,而Sbu2的高度也没有指定,那他会自动占据了除了Sub1和Sub2的所有空间!其实这个例子的布局是非常常见的界面布局例子,比如说酷狗,sub1和sub3分别表示标题栏和状态栏,sub2为程序的主界面:
关于酷狗的更加详细的布局分析可以看我前面写的博客《仿酷狗音乐播放器开发日志——整体框架分析》,几乎每开发酷狗的一部分,我都会把布局分析一下写出来。
把这些知识综合起来,现在就可以写出一个自动调整大小的大致布局了,但是还没法精确控制每个控件的位置。
利用上面介绍的自动占位的特性,我这里举一个标题栏编写的例子:
<HorizontalLayout name="background" height="30" bkimage="file='UI\BKImage\4.jpg' source='0,0,1000,30'" ><!-- 标题栏 -->
<Label text="皮肤与窗口调整——Redrain播放器" textcolor="#FFFFFFFF" textpadding="5,0,0,0" font="1" autocalcwidth="false" width="300"/>
<Control />
<Button name="closebtn" width="23" height="18" padding="0,6,0,0" normalimage="UI\title\close_normal.png" hotimage="UI\title\close_hover.png" pushedimage="UI\title\close_down.png" />
</HorizontalLayout>
这个例子是我的仿酷狗播放器的换肤窗体的标题栏。他的外层是一个横向布局,高度为30,宽度随窗体调整。让标题文字居于左侧,关闭按钮在最右侧,如果让窗体调整宽度后文字和关闭按钮自动调整位置,就需要把中间的空位占满。这时我们就需要一个占位控件,不给他设置width和height属性,这样子他就会自动占据剩余的空间!就达到了相对布局的自动调整位置的效果。这个使用方法是相对布局里非常常用的!而这个占位控件在没有什么其他要求时建议像我给出的例子那样,使用Control控件,因为他是所有控件和容器的祖先基类,代码和属性相对是最少的,这样有利于提高程序的效率!
在充分理解了占位的技巧后,再配合一些微调属性,就可以完美控制各个控件了,这几个微调属性分别是inset、padding、childpadding,这几个属性的介绍如下:
<Attribute name="padding" default="0,0,0,0" type="RECT" comment="外边距,如(2,2,2,2)"/>
<Attribute name="inset" default="0,0,0,0" type="RECT" comment="容器的内边距,如(2,2,2,2)"/>
<Attribute name="childpadding" default="0" type="DWORD" comment="子控件之间的额外距离,如(4)"/>
在float为假,也就是相对布局中,这几个属性才起效。
inset属性
这是给容器控件使用的,使用后他所包含的所有使用相对布局的元素,都会被限制在设置的范围内,适合对容器内所用元素进行整体的坐标控制。比如在前面提到的做360工具箱时,我们使用TileLayout容器来存放每一个工具,我们首先设置inset属性,就可以让所有的工具项限制在一定范围内,例子如下:
<TileLayout name="listctrl" width="540" height="400" inset="20,20,0,20" childpadding="20" vscrollbar="true" columns="2" bkimage="CustomList\List_bk.png" itemhotimage="CustomList\ListItem_bk_hover.png" itemselectedimage="CustomList\ListItem_bk_hover.png">
</TileLayout>
通过设置inset属性,让所有元素限制在一定范围内而不用重复设置每个元素的属性。
childpadding属性:
childpadding属性设置容器内每个元素之间的间距,这个比较容易理解,上面的例子中也用到了childpadding属性。这个属性在不同容器中代表不同意思,在横向布局中代表子控件之间的横向间隔,在纵向布局中代表子控件之间的纵向间隔,在TileLayout容器中代码行与行之间的间隔!
padding属性:
padding属性是相对布局中最常用的属性!用来设置相对于前一个控件的位置,这个属性的控件位置微调的关键。一般只用他的前两个字段,设置左边距和上边距,后两个字段是无效的,或者说存在问题(为什么会有问题?请看源码)。不过使用这两个字段就够了。这是我的仿酷狗播放器的状态的布局代码:
<HorizontalLayout height="30" inset="77,0,0,0" bkimage="UI\statusbar\status_bk.png"><!-- 状态栏 -->
<Button width="31" height="30" padding="0,0,0,0" normalimage="UI\statusbar\add_normal.png" hotimage="UI\statusbar\add_hover.png" pushedimage="UI\statusbar\add_down.png" />
<Button width="31" height="30" padding="40,0,0,0" normalimage="UI\statusbar\locate_normal.png" hotimage="UI\statusbar\locate_hover.png" pushedimage="UI\statusbar\locate_down.png" />
<Button width="31" height="30" padding="40,0,0,0" normalimage="UI\statusbar\search_normal.png" hotimage="UI\statusbar\search_hover.png" pushedimage="UI\statusbar\search_down.png" />
<Button width="31" height="30" padding="40,0,0,0" normalimage="UI\statusbar\setting_normal.png" hotimage="UI\statusbar\setting_hover.png" pushedimage="UI\statusbar\setting_down.png" />
<Control />
<Label text="Redrain仿酷狗音乐盒DirectUI ^_^" textpadding="0,6,0,0" width="30" font="0" />
</HorizontalLayout>
使用padding属性,这是底部这四个按钮的相对位置。如果我想整体让这四个控件向右位移10像素,那么我只要设置第一个按钮的padding属性为padding="10,0,0,0",就可以了,其他布局完全不需要修改!
结束语
说到这里也就把布局的知识总结得差不多了,对于duilib的新手朋友,建议多看看各个demo的布局文件,他们几乎涵盖了所有知识点。然后结合我总结的东西自己手动写几个布局代码,相信很快就可以熟练编写界面布局了。另外我的前面博客里经常会分析布局,大家也可以看看。
我使用duilib的时间也不算长,水平有限,文章中有什么错误或者描述不清,请留言给我,我会及时纠正!