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



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属性设置子元素在容器主轴上的排列方式。可以从主轴起始位置开始排布,也可以从主轴结束位置开始排布,或者均匀分割主轴的空间,对齐的那个方向必须设置大小


.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)
|
布局子元素在交叉轴上的对齐方式
若主轴为垂直方向,则交叉轴为水平方向,反之则垂直方向,对齐的那个方向必须设置大小


//交叉轴在水平方向 .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,在容器主轴方向自动填充空白空间,达到自适应拉伸效果
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%')
|
自适应缩放
子元素随容器尺寸的变化而按照预设的比例自动调整尺寸,适应各种不同大小的设备
- 父容器尺寸确定时,使用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%') }
|
- 父容器尺寸确定时,使用百分比设置子元素和兄弟元素的宽度,使他们在任意尺寸的设备下保持固定的自适应占比
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) } }
|
- 在 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); }); } }
|
- 在组件中使用:
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()
|