
鸿蒙 ArkTS 布局之 RelativeContainer 实战自适应表单一、引言表单是移动端最常见的 UI 场景。一个个人信息登记表要求标签左对齐、输入框自适应拉伸、行间间距一致且在不同屏幕尺寸下表现稳定。传统做法Column Row 嵌套层级过深Flex 弹性布局多行难以精确控制。RelativeContainer相对布局以锚点约束解决这些问题每个子组件独立定位通过声明式alignRules定义相对关系无需嵌套容器。二、核心概念2.1 什么是 RelativeContainer子组件位置通过相对锚定描述可锚定到容器自身内置名__container__同级兄弟组件通过id()2.2 alignRules 机制alignRules({left:{anchor:targetId,align:HorizontalAlign.Start},top:{anchor:targetId,align:VerticalAlign.Bottom},right:{anchor:__container__,align:HorizontalAlign.End}})属性含义字段说明left左边缘anchor锚定目标 id 或__container__top上边缘alignStart/End/Center/Top/Bottomright约束宽度——bottom约束高度——语义示例left:{anchor:label,align:HorizontalAlign.End}// 我的左边缘 对齐 label 的右边缘top:{anchor:input,align:VerticalAlign.Bottom}// 我的上边缘 对齐 input 的下边缘三、需求与完整代码目标表单┌───────────────────────────────────┐ │ 个人信息登记表 │ ├───────────────────────────────────┤ │ 姓 名 [____________________] │ │ 性 别 [____________________] │ │ 手 机 号 [____________________] │ │ 邮 箱 [____________________] │ │ │ │ [ 提 交 ] │ └───────────────────────────────────┘API 24 下Entry、Component、State为内置语法UI 组件全局可用无需 import。EntryComponentstruct RelFormPage{StatenameValue:string;StategenderValue:string;StatephoneValue:string;StateemailValue:string;build(){RelativeContainer(){// 1. 标题Text(个人信息登记表).id(formTitle).fontSize(24).fontWeight(FontWeight.Bold).textAlign(TextAlign.Center).alignRules({top:{anchor:__container__,align:VerticalAlign.Top},left:{anchor:__container__,align:HorizontalAlign.Start},right:{anchor:__container__,align:HorizontalAlign.End}}).margin({top:28,bottom:4})// 2. 分隔线Divider().id(dividerTitle).height(1).color(#E0E0E0).alignRules({top:{anchor:formTitle,align:VerticalAlign.Bottom},left:{anchor:__container__,align:HorizontalAlign.Start},right:{anchor:__container__,align:HorizontalAlign.End}}).margin({top:12})// 3. 姓名Text(姓 名).id(labelName).fontSize(16).fontWeight(FontWeight.Medium).alignRules({top:{anchor:dividerTitle,align:VerticalAlign.Bottom},left:{anchor:__container__,align:HorizontalAlign.Start}}).margin({top:24,left:24})TextInput({placeholder:请输入姓名,text:this.nameValue}).id(inputName).height(44).backgroundColor(#F5F5F5).borderRadius(8).alignRules({top:{anchor:labelName,align:VerticalAlign.Top},left:{anchor:labelName,align:HorizontalAlign.End},right:{anchor:__container__,align:HorizontalAlign.End}}).margin({left:16,right:24}).onChange((v:string){this.nameValuev})// 4. 性别Text(性 别).id(labelGender).fontSize(16).fontWeight(FontWeight.Medium).alignRules({top:{anchor:inputName,align:VerticalAlign.Bottom},left:{anchor:__container__,align:HorizontalAlign.Start}}).margin({top:24,left:24})TextInput({placeholder:请输入性别,text:this.genderValue}).id(inputGender).height(44).backgroundColor(#F5F5F5).borderRadius(8).alignRules({top:{anchor:labelGender,align:VerticalAlign.Top},left:{anchor:labelGender,align:HorizontalAlign.End},right:{anchor:__container__,align:HorizontalAlign.End}}).margin({left:16,right:24}).onChange((v:string){this.genderValuev})// 5. 手机号Text(手 机 号).id(labelPhone).fontSize(16).fontWeight(FontWeight.Medium).alignRules({top:{anchor:inputGender,align:VerticalAlign.Bottom},left:{anchor:__container__,align:HorizontalAlign.Start}}).margin({top:24,left:24})TextInput({placeholder:请输入手机号,text:this.phoneValue}).id(inputPhone).height(44).backgroundColor(#F5F5F5).borderRadius(8).type(InputType.Number).maxLength(11).alignRules({top:{anchor:labelPhone,align:VerticalAlign.Top},left:{anchor:labelPhone,align:HorizontalAlign.End},right:{anchor:__container__,align:HorizontalAlign.End}}).margin({left:16,right:24}).onChange((v:string){this.phoneValuev})// 6. 邮箱Text(邮 箱).id(labelEmail).fontSize(16).fontWeight(FontWeight.Medium).alignRules({top:{anchor:inputPhone,align:VerticalAlign.Bottom},left:{anchor:__container__,align:HorizontalAlign.Start}}).margin({top:24,left:24})TextInput({placeholder:请输入邮箱地址,text:this.emailValue}).id(inputEmail).height(44).backgroundColor(#F5F5F5).borderRadius(8).type(InputType.Email).alignRules({top:{anchor:labelEmail,align:VerticalAlign.Top},left:{anchor:labelEmail,align:HorizontalAlign.End},right:{anchor:__container__,align:HorizontalAlign.End}}).margin({left:16,right:24}).onChange((v:string){this.emailValuev})// 7. 提交按钮Button(提 交).id(submitBtn).height(48).fontSize(18).fontWeight(FontWeight.Medium).borderRadius(24).backgroundColor(#007AFF).fontColor(#FFFFFF).alignRules({top:{anchor:inputEmail,align:VerticalAlign.Bottom},left:{anchor:__container__,align:HorizontalAlign.Start},right:{anchor:__container__,align:HorizontalAlign.End}}).margin({top:40,left:32,right:32}).onClick((){console.info(提交:,this.nameValue,this.genderValue,this.phoneValue,this.emailValue);})}.width(100%).height(100%).backgroundColor(#FFFFFF)}}四、布局详解4.1 标题居中同时约束top、left、right使宽度铺满容器加.textAlign(TextAlign.Center)居中。标准模式。4.2 分隔线top: { anchor: formTitle, align: VerticalAlign.Bottom }上边缘 标题下边缘 margin。无需知道标题高度。4.3 标签-输入框核心模式以姓名为例标签top → dividerTitle.Bottom24pxleft → __container__.Start24px输入框的锚定锚定含义效果top → labelName.Top顶部对齐标签同一水平线left → labelName.End左边缘 标签右边缘16px紧邻标签right → __container__.End右边缘 容器右边缘24px自适应拉伸同时约束leftright宽度自动推导。容器变化时输入框自动伸缩。4.4 行间自动排列链式锚定标题 → 分隔线 → 姓名标签 → 姓名输入框 → 性别标签 → 性别输入框 → 手机号 → 邮箱 → 提交每行顶上对齐上一行的输入框底部。增删行不影响其他组件。4.5 提交按钮左右各 32px margin按钮宽度 容器宽度 - 64px。圆角 24px 蓝底白字。五、自适应原理5.1 横向容器宽度变化时横屏/折叠屏标签宽度不变Text 只约束 left宽度 文字宽度输入框拉伸left 固定锚定标签右侧right 锚定容器右边缘变化值公式输入框宽度 容器宽度 - 标签宽度 - 56px总 margin5.2 纵向增删行只需操作锚定链的首尾节点后续自动排列。5.3 对比 Flexbox概念FlexboxRelativeContainer子项标识无.id(xxx)对齐align-items.alignRules()自适应flex: 1对边同时锚定渲染顺序DOM 顺序锚定链决定六、常见问题循环依赖A 锚 B、B 锚 A 会导致解析失败。确保锚定链有向无环。组件缩为 0只有 topleft 无宽度约束时可能发生。显式设置 height或同时对边约束。margin 叠加margin 在锚定完成后施加偏移。margin({ left: 24 }) 锚定位置 24px。七、最佳实践7.1 扁平结构RelativeContainer 布局树仅一层而 ColumnRow 深 3 层。更少节点 更快渲染。7.2 封装复用每行锚定模式相同可封装自定义组件Componentstruct FormRow{Proplabel:stringPropplaceholder:stringStatetext:stringProptopAnchor:stringbuild(){RelativeContainer(){Text(this.label).id(label).fontSize(16).alignRules({top:{anchor:this.topAnchor,align:VerticalAlign.Bottom},left:{anchor:__container__,align:HorizontalAlign.Start}}).margin({top:24,left:24})TextInput({placeholder:this.placeholder,text:this.text}).id(input).height(44).backgroundColor(#F5F5F5).borderRadius(8).alignRules({top:{anchor:label,align:VerticalAlign.Top},left:{anchor:label,align:HorizontalAlign.End},right:{anchor:__container__,align:HorizontalAlign.End}}).margin({left:16,right:24}).onChange((v:string){this.textv})}.width(100%).height(68)}}使用FormRow({label:姓 名,placeholder:请输入姓名,topAnchor:dividerTitle})FormRow({label:性 别,placeholder:请输入性别,topAnchor:inputName})7.3 调试检查 id 唯一性检查锚定链是否断裂锚定到不存在的 id用 DevEco Studio Inspector 查看约束八、扩展场景8.1 居中 正方形Image($r(app.media.avatar)).id(avatar).alignRules({center:{anchor:__container__,align:VerticalAlign.Center},middle:{anchor:__container__,align:HorizontalAlign.Center}}).width(80%).aspectRatio(1)8.2 角标Text(3).id(badge).fontSize(12).fontColor(#FFFFFF).backgroundColor(#FF3B30).borderRadius(10).width(20).height(20).textAlign(TextAlign.Center).alignRules({top:{anchor:avatar,align:VerticalAlign.Top},right:{anchor:avatar,align:HorizontalAlign.End}}).margin({top:-6,right:-6})九、总结通过个人信息登记表实战本文展示了 RelativeContainer 在自适应表单中的完整方案。核心要点锚点约束子组件独立声明边与锚点的关系引擎自动计算标准模式标签 left→容器输入框 left→标签.rightright→容器扁平高性能比 ColumnRow 嵌套渲染更快声明式 命令式描述组件间关系而非组件位置可复用自定义组件封装锚定模式适用场景场景推荐理由复杂表单⭐⭐⭐⭐⭐标签左对齐 输入框自适应自定义弹窗⭐⭐⭐⭐标题/内容/按钮自由定位Dashboard⭐⭐⭐⭐多卡片精确排列个人资料页⭐⭐⭐⭐头像/信息/按钮多区域布局