在ACE Java UI框架中,提供了两种编写布局的方式:代码方式创建布局和XML方式声明UI布局。
这两种方式创建出的布局没有本质差别,在XML中声明布局,加载后同样可在代码中对该布局进行修改。
XML声明布局的方式更加简便直观。每个Component和ComponentContainer对象中的大部分属性支持在XML中进行设置,它们都有各自的XML属性列表。某些属性仅适用于特定的组件,例如:只有Text组件支持text_color属性,但如果不支持该属性的组件添加了该属性,则该属性会被忽略。具有继承关系的组件子类将继承父类的属性列表,Component作为组件的基类,拥有各个组件常用的属性,例如ID、布局参数等。
编程创建布局的方式比较麻烦,需要在AbilitySlice中分别创建组件和布局,并将它们进行组织关联。
XML声明布局的方式更加简便直观。项目默认使用XML的方式创建页面布局。
图3-23 通过DevEco Studio创建XML布局
在DevEco Studio的Project窗口,打开entry→src→main→resources→base,右击layout文件夹,选择New→File,将文件命名为first_layout.xml,如图3-23所示。
打开新创建的first_layout.xml布局文件,修改其中的内容,对布局和组件的属性和层级进行描述。
如代码示例3-4所示。
代码示例3-4 编写界面布局:first_demo/entry/layout/first_layout.xml
在上面的XML布局中,向组件添加的宽和高分为有固定的单位vp、match_parent和match_content。
在代码中需要加载XML布局,并添加为根布局或作为其他布局的子Component。如代码示例3-5所示。
代码示例3-5 编写界面布局:first_demo/entry/java/ExampleAbilitySlice.java
代码创建布局需要在AbilitySlice中分别创建组件和布局,并将它们进行组织关联。
创建一个方向布局(DirectionalLayout)组件,代码如下:
设置方向布局大小,代码如下:
设置方向布局属性,代码如下:
将组件添加到方向布局中(视布局需要对组件所设置的布局属性进行约束),button为已在3.3.2节中定义的Button组件,代码如下:
将布局添加到组件树中,代码如下:
上面创建好了一个布局,接下来需要将一些组件放在布局中,例如声明一个Button组件,代码如下:
设置Botton组件大小,代码如下:
设置Botton组件属性,代码如下:
下面通过编程的方式创建一个简单的布局页面,页面显示效果如图3-24所示。
根据以上步骤创建组件和布局后的界面显示效果如图3-24所示。如代码示例3-6所示,为组件设置了一个按键回调,在按键被按下后,应用会执行自定义的操作。
代码示例3-6 页面逻辑:first_demo/entry/java/ExampleAbilitySlice.java
图3-24 通过编程的方式创建布局
在代码示例中,可以看到设置组件大小的方法有两种:
这两种方法的区别是后者还可以增加更多的布局属性设置,例如使用alignment设置水平居中的约束。另外,这两种方法设置的宽和高以最后设置的数值作为最终结果。它们的取值一致,可以是以下取值:
在Java UI框架中,具体的布局类通常以XXLayout命名,如DirectionalLayout、 DependentLayout等,如图3-25所示。完整的用户界面是一个布局,用户界面中的一部分也可以是一个布局。鸿蒙的常见布局有定向布局、依赖布局、位置布局、堆叠布局、自适应布局。
图3-25 鸿蒙常见的布局分类
Java UI框架提供了标准布局功能的容器,都继承自ComponentContainer。有些组件继承自StackLayout,但命名没有以Layout结尾,如ScrollView、PageSlider等。ListContainer组件同样继承自ComponentContainer。
DirectionalLayout布局类似于Android中的LinearLayout布局。
DirectionalLayout的orientation属性的取值有两个:horizontal和vertical,代表横向和纵向布局。
DirectionalLayout用于将一组组件(Component)按照水平或者垂直方向排布,能够方便地对齐布局内的组件。
定向布局有几个重要的属性:排列属性、对齐方式属性、权重属性,接下来分别看一看这些属性的用法。
1)排列属性
DirectionalLayout布局分为两种模式:vertical垂直排列子元素,horizontal水平排列子元素,如图3-26所示。
如果垂直排列的子元素height的总和超过了父元素,则会被截取。如果水平排列的子元素width的总和超过了父元素,则会被截取。
在下面的案例中,首先在一个DirectionalLayout布局组件内,放置3个DirectionalLayout子组件。外部DirectionalLayout布局组件通过将orientation属性值设置为vertical或者通过horizontal来控制子组件的排列方向,如代码示例3-7所示。
图3-26 DirectionalLayout布局分为两种模式:水平模式和垂直模式
代码示例3-7 DirectionalLayout: first_demo/layout/ability_second.xml
在上面的代码中子组件的宽和高没有超出屏幕的宽和高,但是如果垂直排列的子元素height的总和超过了父元素,则会被截取。如果水平排列的子元素width的总和超过了父元素,则会被截取。我们来看下面的例子,如图3-27所示。因为水平和垂直方向上的宽和高超出屏幕的宽和高,所以超出部分被截取。
图3-27 DirectionalLayout布局超出部分被截取
2)对齐方式属性
对齐方式属性列表,如表3-3所示。
表3-3 对齐方式属性列表
将布局的方向设置为horizontal后,分别将内部的布局组件的对齐方式属性设置为top、vertical_center、bottom,显示效果如图3-28所示。
图3-28 将布局的方向设置为horizontal后的效果
这种设置方式的问题是,内部的3个组件的宽度是累加的,第一个组件的宽度为120vp,中间的组件使用顶部的组件的宽度为起点进行累加,所以到了第3个组件是以中间的组件的右边为开始显示在底部,如果组件的宽度超出屏幕,将只显示剩余部分的内容。
将布局的方向设置为vertical后,分别将内部的布局组件的对齐方式属性设置为left、horizontal_center、right,显示效果如图3-29所示。
图3-29 将布局的方向设置为vertical后的效果
3)权重属性
权重(weight)就是按比例来分配组件占用父组件的大小,如图3-30所示。
图3-30 权重(weight)按比例分配组件占用父组件的大小
父布局可分配宽度=父布局宽度-所有子组件width之和。
组件宽度=(组件weight/所有组件weight之和)×父布局可分配宽度。
将外部布局组件的方向属性设置为horizontal,内部的子组件不需要设置宽度,这里设置为0vp,每个子组件通过weight的比例值设置所占父容器的宽度比。
DependentLayout是JavaUI系统里的一种常见布局。与DirectionalLayout相比,拥有更多的排布方式,每个组件可以指定相对于其他同级元素的位置,或者指定相对于父组件的位置。
1)排列方式
相对于同级组件的位置布局,如表3-4所示。
表3-4 相对于同级组件的位置布局
相对于父组件的位置布局,如表3-5所示。
表3-5 相对于父组件的位置布局
相对于同级组件的位置布局,如图3-31所示。
2)相对父控件布局
第1个Text组件被设置在相对于父容器组件的右边align_parent_right="true";第2个Text组件被设置在相对于父容器的底部中间位置:这里设置了align_parent_bottom= "true"和center_in_parent="true";第3个Text组件被设置在父容器的正中间:center_in _parent="true";第4个Text组件被设置在父容器的左上方:align_parent_left="true"和align_parent_top="true",如图3-32所示。
图3-31 相对于同级组件的位置布局
图3-32 相对父控件布局
3)复杂界面布局
在依赖布局中的第1个Text组件,无须任何设置,第2个Text组件内容为Catalog,只需相对于Text1组件进行布局:ohos:below="$id:text1";第3个Text组件内容为Content,这个组件同样相对于Text1组件布局,位于Text1组件下方及Text2组件右边,所以需要设置:ohos:below="$id:text1"和ohos:end_of="$id:text2";第2个Button组件内容为Next,这个组件被设置在相对于父容器的底部和右边位置,即设置为align_parent_end= "true";第1个Button组件只需相对于第2个Button布局:left_of="$id:button2"。
图3-33 复杂界面布局
下面我们通过XML的方式实现图3-33所示效果,如代码示例3-8所示。
代码示例3-8 依赖布局的复杂布局:first_demo/entry/layout/ability_third.xml
TableLayout使用表格的方式划分子组件。TableLayout类似于Android中的TableLayout,以列表的方式展示内容,使用TableLayout时,通过设置行数和列数来控制表格内组件的行和列,如图3-34所示。
图3-34 设置行数和列数
下面实现如图3-34所示的效果,将row_count和column_count设置为两行三列的布局,如代码示例3-9所示。
代码示例3-9 表格布局:first_demo/entry/layout/ability_forth.xml
可以通过下面的属性设置对齐方式,如表3-6所示。
表3-6 设置Table布局的对齐方式
align_edges表示子组件边界对齐,此方式为默认对齐方式。
这里将第一个Text组件的margin设置为"18vp",将其他的Text组件设置为"8vp"。如图3-35所示,第二行的组件默认使用屏幕的边界对齐方式。
图3-35 设置行数和列数
align_contents表示子组件边距对齐。
上面的布局,如果把alignment_type设置为align_contents的值,则第二行的组件将按第一行的组件对齐,如图3-36所示。
图3-36 设置行数和列数
PositionLayout为绝对布局,该布局指定了子组件在其中的具体位置( x/y 坐标)。由于需要指定子组件的 x/y 精确坐标,其布局的灵活性相对较差,在没有绝对定位的情况下相比其他类型的布局更加难以维护,因此不建议使用。
我们通过PositionLayout布局实现上面的UI图,如图3-37所示。
图3-37 PositionLayout绝对布局
(1)以PositionLayout为父组件,宽和高占满整个屏幕。
(2)接下来先进行组件占位,两个TextField和一个Button组件。
(3)根据UI图的比例,让组件左上角( x/y )为起始点,相对父组件的距离进行移动。如代码示例3-10所示。
代码示例3-10 位置布局:first_demo/entry/layout/ability_six.xml
我们发现运行后文本框置于整个布局的左上角,并没有到达我们想要的位置,这是因为没有给其绝对位置的定位。我们在MainAbilitySlice中根据ID找到我们的文本框,然后将其绝对位置 x/y 轴距离进行定位。
另外两个组件和第一个文本组件一样,找到绝对位置即可实现,如代码示例3-11所示。
代码示例3-11 位置布局案例:first_demo/entry/layout/ability_six.xml
通过编码的方式指定每个组件的位置坐标,通过setContentPosition方法设置 x 轴和 y 轴坐标,如代码示例3-12所示。
代码示例3-12 位置布局控制组件:first_demo/entry/java/SixAbility.java
图3-38 StackLayout布局
至此,完成了绝对布局中组件的绝对定位,但不建议在项目中使用该布局,因为如果设备分辨率发生变化,则整个布局会出现错乱。
StackLayout直接在屏幕上开辟出一块空白的区域,添加到这个布局中的视图都以层叠的方式显示,而它会把这些视图默认放到这块区域的左上角,第一个添加到布局中的视图显示在最底层,最后一个被放在最顶层,上一层的视图会覆盖下一层的视图,如图3-38所示。
StackLayout布局有以下特征:
使用默认布局添加组件,StackLayout中组件的布局默认在区域的左上角,并且以后创建的组件会堆叠在上层。
具体的代码实现,如代码示例3-13所示。
代码示例3-13 堆叠布局:first_demo/entry/layout/ability_seven.xml
使用相对位置添加组件,如代码示例3-14所示。使用layout_alignment属性可以指定组件在StackLayout中的相对位置,如将Button组件添加至StackLayout的右面,如图3-39所示。
图3-39 使用layout_alignment属性
代码示例3-14 使用相对位置添加组件
将子视图从后面移到前面显示,如代码示例3-15所示,单击moveChildToFront方法让底部组件移动到最前面。
代码示例3-15 将子视图从后面移到前面显示
AdaptiveBoxLayout意为自适应盒模式布局,是将整个UI划分为相同宽度,但高度有可能不同的行和列的盒子,也可以理解为将整个UI划分为多块。其中盒子的宽度取决于布局的宽度和每行中盒子的数量,这个需要在布局策略中指定。子组件的排列只有在前一行被填满后才会开始在新一行中占位。
自适应布局AdaptiveBoxLayout的特点如下。
(1)布局中盒子(块)的宽度取决于布局的宽度和每行中盒子的数量,这些属性需要在布局策略中指定。
(2)每个盒子(块)的高度由其子组件的高度决定。
(3)子组件的排列只有在前一行被填满后才会开始在新一行中占位。
(4)每个盒子(块)包含一个子组件。
(5)每一行的高度由该行最高盒子的高度决定。
(6)布局的宽度只能为MATCH_PARENT或固定值。
(7)可以为布局中的组件设置长度、宽度和对齐方式。
如图3-40所示的相册中的图片会随着屏幕的变化自动选择适当的方式显示图片,如在大屏TV上方向布局使用水平方向,在小屏手机上方向布局使用垂直方向。
图3-40 通过AdaptiveBoxLayout实现的自适应相册
我们来看一看如何实现图3-40的布局效果,如代码示例3-16所示。
这里创建一个AdaptiveBoxLayout布局组件,在AdaptiveBoxLayout组件中定义了两个DirectionalLayout组件,这两个组件会随着屏幕大小的改变自动选择以水平还是以垂直方式显示。
代码示例3-16 自适应布局:first_demo/entry/layout/ability_eight.xml
实现完整效果如代码示例3-17所示。
代码示例3-17 自适应布局:first_demo/entry/layout/ability_eight.xml