4.界面布局、动画

布局思路:先排版,再放内容,再美化

线性布局文档中心

Column容器内子元素排列示意图

Row行Column列:默认有居中效果

通过space属性设置排列方向上子元素的间距

Column({ space: 数字 }){}
Row({ space: 数字 }){}
build() {
Column({ space: 20 }) {
Text('space: 20').fontSize(15).fontColor(Color.Gray).width('90%')
Row().width('90%').height(50).backgroundColor(0xF5DEB3)
Row().width('90%').height(50).backgroundColor(0xD2B48C)
Row().width('90%').height(50).backgroundColor(0xF5DEB3)
}.width('100%')
}

布局子元素在主轴上的排列方式

在布局容器内,可以通过justifyContent属性设置子元素在容器主轴上的排列方式。可以从主轴起始位置开始排布,也可以从主轴结束位置开始排布,或者均匀分割主轴的空间,对齐的那个方向必须设置大小

Column容器内子元素在垂直方向上的排列图Row容器内子元素在水平方向上的排列图

.justifyContent(FlexAlign.枚举值)
Column({}) {
Column() {
}.width('80%').height(50).backgroundColor(0xF5DEB3)

Column() {
}.width('80%').height(50).backgroundColor(0xD2B48C)
}
.width('100%').height(300).backgroundColor('rgb(242,242,242)')
.justifyContent(FlexAlign.Start)

Row({}) {
Column() {
}.width('20%').height(30).backgroundColor(0xF5DEB3)

Column() {
}.width('20%').height(30).backgroundColor(0xD2B48C)
}
.width('100%').height(200).backgroundColor('rgb(242,242,242)')
.justifyContent(FlexAlign.Start)

布局子元素在交叉轴上的对齐方式

若主轴为垂直方向,则交叉轴为水平方向,反之则垂直方向,对齐的那个方向必须设置大小

Column容器内子元素在水平方向上的排列图Row容器内子元素在垂直方向上的排列图

//交叉轴在水平方向
.alignItems(HorizontalAlign.枚举值)
//交叉轴在垂直方向
.alignItems(VerticalAlign.枚举值)
Column({}) {
Column() {
}.width('80%').height(50).backgroundColor(0xF5DEB3)

Column() {
}.width('80%').height(50).backgroundColor(0xD2B48C)
}
.width('100%').backgroundColor('rgb(242,242,242)')
.alignItems(HorizontalAlign.Start)

Row({}) {
Column() {
}.width('20%').height(30).backgroundColor(0xF5DEB3)

Column() {
}.width('20%').height(30).backgroundColor(0xD2B48C)
}
.width('100%').height(200).backgroundColor('rgb(242,242,242)')
.alignItems(VerticalAlign.Top)

自适应拉伸

常用空白填充组件Blank,在容器主轴方向自动填充空白空间,达到自适应拉伸效果

Blank()
Column() {
Row() {
Text('Bluetooth').fontSize(18)
Blank()
Toggle({ type: ToggleType.Switch, isOn: true })
}.backgroundColor(0xFFFFFF).borderRadius(15).padding({ left: 12 }).width('100%')
}.backgroundColor(0xEFEFEF).padding(20).width('100%')

自适应缩放

子元素随容器尺寸的变化而按照预设的比例自动调整尺寸,适应各种不同大小的设备

  1. 父容器尺寸确定时,使用layoutWeight属性设置子元素和兄弟元素在主轴上的权重,忽略元素本身尺寸设置,使它们在任意尺寸的设备下自适应占满剩余空间
.layoutWeight(数字)		//数字越大权重越高
Column() {
Text('1:2:3').width('100%')
Row() {
Column() {
Text('layoutWeight(1)')
.textAlign(TextAlign.Center)
}.backgroundColor(0xF5DEB3).height('100%')
.layoutWeight(1)

Column() {
Text('layoutWeight(2)')
.textAlign(TextAlign.Center)
}.backgroundColor(0xD2B48C).height('100%')
.layoutWeight(2)

Column() {
Text('layoutWeight(3)')
.textAlign(TextAlign.Center)
}.backgroundColor(0xF5DEB3).height('100%')
.layoutWeight(3)
}.backgroundColor(0xffd306).height('30%')
}
  1. 父容器尺寸确定时,使用百分比设置子元素和兄弟元素的宽度,使他们在任意尺寸的设备下保持固定的自适应占比
Column() {
Row() {
Column() {
Text('left width 20%')
.textAlign(TextAlign.Center)
}.width('20%').backgroundColor(0xF5DEB3).height('100%')

Column() {
Text('center width 50%')
.textAlign(TextAlign.Center)
}.width('50%').backgroundColor(0xD2B48C).height('100%')

Column() {
Text('right width 30%')
.textAlign(TextAlign.Center)
}.width('30%').backgroundColor(0xF5DEB3).height('100%')
}.backgroundColor(0xffd306).height('30%')
}

自适应延伸

当一屏无法完全显示时,可以在Column或Row组件的外层包裹一个可滚动的容器组件Scroll来实现可滑动的线性布局

Scroll有大小限制,且小于滚动内容区域,Scroll API

控制名:Scroller = new Scroller()		//创建Scroll控制器
Scroll( this.控制名 ) { //this.控制名将Scroll与Scroll控制器绑定
Column() {}

this.scroller.枚举值(不同枚举值参数不同)
}
.scrollable(ScrollDirection.枚举值) // 滚动方向
.scrollBar(BarState.枚举值) // 滚动条常驻显示
.scrollBarColor(Color.枚举值) // 滚动条颜色
.scrollBarWidth(数字) // 滚动条宽度
.edgeEffect(EdgeEffect.枚举值) // 滚动到边沿后回弹
@Entry
@Component
struct ScrollCase {
@State message: string = 'Hello World';

scroller:Scroller = new Scroller()

build() {
Column() {
toubu()
Scroll(this.scroller){
Column({space:10}){
Button('回到底部')
.onClick(() =>{
this.scroller.scrollEdge(Edge.End)
})

ScrollItemComp()
ScrollItemComp()
ScrollItemComp()
ScrollItemComp()
Button('回到顶部')
.onClick(() =>{
this.scroller.scrollEdge(Edge.Start)
})
}.padding(10)
}
.layoutWeight(1)
jiaodi()
}
.height('100%')
.width('100%')
}
}

@Component
struct ScrollItemComp {
build() {
Column(){
Text('卡片')
}
.width('100%')
.height(200)
.backgroundColor(Color.Pink)
}
}

弹性布局文档中心

在渲染时存在二次布局过程,因此在对性能有严格要求的场景下建议使用Column、Row代替,不规则的时候需要换行,才使用,如下图所示:

Flex({
direction: FlexDirection.枚举值, //主轴方向
justifyContent: FlexAlign.枚举值, //主轴对齐方式
alignItems: ItemAlign.枚举值, //交叉轴对齐方式
wrap: FlexWrap.枚举值, //布局换行
space: {
main: LengthMetrics.枚举值(数字), //主轴间距
cross: LengthMetrics.枚举值(数字) //交叉轴间距
}
}){
//组件
}
//FlexWrap.NoWrap 单行布局
//FlexWrap.Wrap 多行布局
Column(){
Flex({
direction: FlexDirection.Row, //设置主轴方向
justifyContent: FlexAlign.SpaceAround, //主轴对齐方式
alignItems: ItemAlign.End, //交叉轴对齐方式
wrap: FlexWrap.NoWrap //布局换行
}){
Text()
.width(100)
.height(100)
.backgroundColor( Color.Blue)
.margin(10)
Text()
.width(100)
.height(100)
.backgroundColor(Color.Orange)
.margin(10)
}
.width('100%')
.height(500)
.backgroundColor('#ece')
}

相对布局文档中心

子元素可以指定兄弟元素或父容器作为锚点,基于锚点进行相对位置布局

父元素标识默认为“container”,其他子元素的组件标识(id)

RelativeContainer() {
}
.alignRules({ //相对于锚点的对齐位置
//自己的上top中center下bottom 或 自己的左lef中middle右right
center: {
anchor: '标识字符', //参照物,__container__:参照父容器
align: VerticalAlign.枚举值 //对齐参照物哪条边,但移动是边的交叉轴方向
// align: HorizontalAlign.枚举值
}
})
.id("标识字符") //子元素的组件标识
Row(){
Text('1')
}.width('33%').height('33%')
.backgroundColor(Color.Red)
.id('row4')
.alignRules({
top:{
anchor: 'row2',
align: VerticalAlign.Bottom
},
middle:{
anchor: 'row1',
align: HorizontalAlign.Center
}
})

层叠布局文档中心

卡片层叠效果,层叠操作更简洁,编码效率高(绝对定位的优势是更灵活)

子元素的顺序为 Item1 -> Item2 -> Item3(越往后层级越高)也可以zIndex手动调层级(若手动则每一层都需要手动设置)

Stack({
alignContent: Alignment.枚举值 //层叠方向
}) {
Item1()
Item2()
Item3()
}
Column(){
Stack({
alignContent: Alignment.Bottom
}){
Text('1')
.width(250)
.height(250)
.backgroundColor(Color.Brown)
//.zIndex(1)
Text('2')
.width(200)
.height(200)
.backgroundColor(Color.Blue)
//.zIndex(2)
Text('3')
.width(150)
.height(150)
.backgroundColor(Color.Green)
//.zIndex(3)
}
}

列表布局文档中心

控制名:Scroller = new Scroller()		//创建Scroll控制器
List({ scroller: this.控制名 }) {
ListItem() {
//列表
}
}
.listDirection(Axis.枚举值) //列表方向,默认垂直方向
.divider({
strokeWidth: 数字, //分割线宽度
color: 颜色格式 //分隔线颜色
})
.onScrollStart(() => {}) //滑动开始时触发
.onScrollStop(() => {}) //滑动停止时触发
.onReachEnd(() => {}) //到达末尾位置时触发
@State isEnd: boolean = false
List() {
ListItem() {
Text('北京').fontSize(24)
}
}
.divider({ strokeWidth: 10, color: Color.Blue })
//下拉更新数据
.onScrollStart(() => {
this.isEnd = false
})
.onScrollStop(() => {
setTimeout(() => {
if (this.isEnd) {
const list: number[] = Array(10).fill(Math.random())
this.list.push(...list)
}
}, 1000)
})
.onReachEnd(() => {
this.isEnd = true
})

网格+栅格布局文档中心

一般使用在固定行列的布局

//网格
Grid(){
GridItem(){ //每个小方格
//小方格中写的内容
}
}
.columnsTemplate('1fr 1fr 1fr') //列有几个1fr就有几个小方格,1代表小方格所占的空间份数
.columnsGap(10) //列方向小方块之间的间隙
.rowsTemplate('1fr 1fr 1fr') //行有几个1fr就有几个小方格,1代表小方格所占的空间份数
.rowsGap(10) //行方向小方块之间的间隙
//栅格
GridRow({ columns: 数字, gutter: 数字}){
GridCol(){
//栅格中写的内容
}
}
GridRow({ columns: {
xs: 数字,
sm: 数字,
md: 数字,
lg: 数字,
xl: 数字,
xxl: 数字
}, gutter: 数字})
//columns: 数字,设置列个数
//gutter: 数字,设置间距
//网格
Grid(){
GridItem(){
Column(){

}
.width(80)
.height(80)
.backgroundColor(Color.Green)
}
GridItem(){
Column(){

}
.width(80)
.height(80)
.backgroundColor(Color.Green)
}
//...
}
.columnsTemplate('1fr 1fr 1fr')
.columnsGap(10)
.rowsTemplate('1fr 1fr 1fr')
.rowsGap(10)

//栅格
GridRow({ columns: 3, gutter: 10}){
GridCol(){
Column(){
Text('卡片')
}
.width('100%')
.height(200)
.backgroundColor(Color.Pink)
}
GridCol(){
Column(){
Text('卡片')
}
.width('100%')
.height(200)
.backgroundColor(Color.Pink)
}
}

软键盘避让模式文档中心

  1. 在 EntryAbility 的使用:
import { KeyboardAvoidMode, window } from '@kit.ArkUI'
import { UIAbility } from '@kit.AbilityKit'

export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index', (err) => {
// 压缩模式
windowStage.getMainWindowSync().getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.RESIZE);
});
}
}
  1. 在组件中使用:
import { KeyboardAvoidMode, window } from '@kit.ArkUI'

@Component
export struct TestComp {
aboutToAppear(): void {
window.getLastWindow(getContext())
.then(win => {
win.getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.RESIZE)
})
}
}

收起软键盘文档中心

this.getUIContext().getFocusController().clearFocus(); //收起软键盘

应用沉浸式效果文档中心

.expandSafeArea([SafeAreaType.SYSTEM])    //隐藏状态和控制栏
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]) //隐藏状态和控制栏

应用深浅色适配文档中心

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
AppStorage.setOrCreate('context', this.context)
}
aboutToAppear() {
theme.initTheme()
}
import { ConfigurationConstant } from '@kit.AbilityKit'

export const ColorModeKey = 'hc-color-mode'

class Theme {
initTheme() {
// 持久化颜色主题,默认值亮色模式
PersistentStorage.persistProp<ConfigurationConstant.ColorMode>(ColorModeKey,
ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT)
// 取出现在应用存储的颜色主题模式
const colorMode = AppStorage.get<ConfigurationConstant.ColorMode>(ColorModeKey)
// 设置应用颜色主题模式
this.setTheme(colorMode!)
}

setTheme(mode: ConfigurationConstant.ColorMode) {
AppStorage.set<ConfigurationConstant.ColorMode>(ColorModeKey, mode)
const ctx = AppStorage.get<Context>('context')
ctx?.getApplicationContext().setColorMode(mode)
}

// 跟随系统
notSet() {
this.setTheme(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET)
}

// 暗色
setDark() {
this.setTheme(ConfigurationConstant.ColorMode.COLOR_MODE_DARK)
}

// 亮色
setLight() {
this.setTheme(ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT)
}
}

export const theme = new Theme()