文章目录前言设计思路状态驱动样式完整实现PC端布局探索工具动画让切换效果更直观代码生成面板写在最后前言写布局教程的人都知道这个问题用文字描述Start/Center/End的区别读者很难直观感受。最好的方式是做一个可交互的演示——点击按钮切换模式实时看到效果变化。ArkUI 做这种演示页非常顺手。核心思路是把演示参数存成State变量UI 样式绑定到变量点击事件只负责改变量其他的让框架自己更新。这篇文章就来把这种演示页的设计模式讲清楚顺带展示 PC 端如何实现一个完整的布局属性探索工具。设计思路状态驱动样式演示页的本质是选择不同的配置 → 看到不同的效果。把当前选中的配置存为State把样式绑定到这个状态就完成了核心逻辑StateactiveJustify:FlexAlignFlexAlign.Start// 点击切换Button(Center).onClick(()this.activeJustifyFlexAlign.Center)// 效果绑定Column(){/* 演示内容 */}.justifyContent(this.activeJustify).animation({duration:250})// 切换时有过渡动画加上.animation({ duration: 250 })让切换有一个平滑过渡演示效果更直观。完整实现PC端布局探索工具完整示例PcLayoutExplorerPage.etsimport{router}fromkit.ArkUIinterfaceAlignOption{label:stringvalue:FlexAlign}interfaceHAlignOption{label:stringvalue:HorizontalAlign}EntryComponentstruct PcLayoutExplorerPage{StatejustifyContent:FlexAlignFlexAlign.StartStatealignItems:HorizontalAlignHorizontalAlign.StartStatecolumnSpace:number8StatedemoChildCount:number3StatecontainerHeight:number300privatejustifyOptions:AlignOption[][{label:Start,value:FlexAlign.Start},{label:Center,value:FlexAlign.Center},{label:End,value:FlexAlign.End},{label:SpaceBetween,value:FlexAlign.SpaceBetween},{label:SpaceAround,value:FlexAlign.SpaceAround},{label:SpaceEvenly,value:FlexAlign.SpaceEvenly},]privatealignOptions:HAlignOption[][{label:Start,value:HorizontalAlign.Start},{label:Center,value:HorizontalAlign.Center},{label:End,value:HorizontalAlign.End},]privatechildColors:string[][#3B82F6,#8B5CF6,#10B981,#F59E0B,#EF4444]privatechildWidths:string[][40%,60%,35%,55%,45%]privategetChildItems():number[]{constitems:number[][]for(leti0;ithis.demoChildCount;i){items.push(i)}returnitems}BuilderoptionButtonT(label:string,current:T,target:T,onSelect:()void){Text(label).fontSize(11).fontColor(JSON.stringify(current)JSON.stringify(target)?#3B82F6:#6B7280).backgroundColor(JSON.stringify(current)JSON.stringify(target)?#EFF6FF:#F3F4F6).borderRadius(6).padding({left:8,right:8,top:5,bottom:5}).border(JSON.stringify(current)JSON.stringify(target)?{width:1,color:#BFDBFE}:{width:0,color:Color.Transparent}).animation({duration:150}).onClick(onSelect)}BuildersectionTitle(title:string,desc:string){Column({space:3}){Text(title).fontSize(13).fontColor(#111827).fontWeight(FontWeight.Medium)Text(desc).fontSize(11).fontColor(#9CA3AF)}.alignItems(HorizontalAlign.Start).width(100%).padding({bottom:8}).border({width:{bottom:1},color:#F3F4F6}).margin({bottom:10})}build(){Row(){// ── 左侧控制面板 ──Scroll(){Column({space:20}){Text(Column 布局探索器).fontSize(16).fontWeight(FontWeight.Bold).fontColor(#111827).padding({bottom:4})// justifyContent 控制Column({space:10}){this.sectionTitle(justifyContent,控制子项在垂直方向主轴的分布方式)Flex({wrap:FlexWrap.Wrap}){ForEach(this.justifyOptions,(opt:AlignOption){Text(opt.label).fontSize(11).fontColor(this.justifyContentopt.value?#3B82F6:#6B7280).backgroundColor(this.justifyContentopt.value?#EFF6FF:#F3F4F6).borderRadius(6).padding({left:8,right:8,top:5,bottom:5}).margin({right:6,bottom:6}).border(this.justifyContentopt.value?{width:1,color:#BFDBFE}:{width:0,color:Color.Transparent}).onClick(()this.justifyContentopt.value)})}}// alignItems 控制Column({space:10}){this.sectionTitle(alignItems,控制子项在水平方向交叉轴的对齐位置)Row({space:6}){ForEach(this.alignOptions,(opt:HAlignOption){Text(opt.label).fontSize(11).fontColor(this.alignItemsopt.value?#3B82F6:#6B7280).backgroundColor(this.alignItemsopt.value?#EFF6FF:#F3F4F6).borderRadius(6).padding({left:8,right:8,top:5,bottom:5}).border(this.alignItemsopt.value?{width:1,color:#BFDBFE}:{width:0,color:Color.Transparent}).onClick(()this.alignItemsopt.value)})}}// space 控制Column({space:10}){this.sectionTitle(space,子项间距${this.columnSpace}vp)Slider({value:this.columnSpace,min:0,max:40,step:4}).width(100%).onChange((v)this.columnSpaceMath.round(v))}// 子项数量Column({space:10}){this.sectionTitle(子项数量,当前${this.demoChildCount}个)Row({space:6}){ForEach([2,3,4,5],(count:number){Text(${count}).fontSize(12).fontColor(this.demoChildCountcount?#3B82F6:#6B7280).backgroundColor(this.demoChildCountcount?#EFF6FF:#F3F4F6).borderRadius(6).width(36).height(32).textAlign(TextAlign.Center).onClick(()this.demoChildCountcount)})}}// 容器高度Column({space:10}){this.sectionTitle(容器高度,${this.containerHeight}vp)Slider({value:this.containerHeight,min:180,max:420,step:20}).width(100%).onChange((v)this.containerHeightMath.round(v))}// 当前配置代码Column({space:6}){Text(当前配置代码).fontSize(12).fontColor(#374151).fontWeight(FontWeight.Medium)Text([Column({ space:${this.columnSpace}}) {,//${this.demoChildCount}个子项,},.alignItems(HorizontalAlign.${this.alignOptions.find(oo.valuethis.alignItems)?.label}),.justifyContent(FlexAlign.${this.justifyOptions.find(oo.valuethis.justifyContent)?.label}),.height(${this.containerHeight}),].join(\n)).fontSize(11).fontColor(#374151).fontFamily(monospace).backgroundColor(#F9FAFB).borderRadius(8).padding(12).width(100%).border({width:1,color:#E5E7EB})}}.padding(20).alignItems(HorizontalAlign.Start)}.width(300).height(100%).scrollBar(BarState.Off).backgroundColor(Color.White).border({width:{right:1},color:#F3F4F6})// ── 右侧效果预览 ──Column(){Text(实时预览).fontSize(16).fontWeight(FontWeight.Bold).fontColor(#111827).padding({bottom:4}).alignSelf(ItemAlign.Start)// 容器边框指示Stack(){// 背景格线Column({space:0}){ForEach([0,1,2,3,4],(_:number){Column().width(100%).layoutWeight(1).border({width:{bottom:1},color:#F3F4F6})})}.width(100%).height(this.containerHeight)// Column 演示Column({space:this.columnSpace}){ForEach(this.getChildItems(),(idx:number){Text(子项${idx1}).fontSize(13).fontColor(Color.White).backgroundColor(this.childColors[idx%this.childColors.length]).borderRadius(6).width(this.childWidths[idx%this.childWidths.length]).height(36).textAlign(TextAlign.Center).animation({duration:250,curve:Curve.EaseInOut})})}.width(100%).height(this.containerHeight).alignItems(this.alignItems).justifyContent(this.justifyContent).animation({duration:250,curve:Curve.EaseInOut})}.width(100%).height(this.containerHeight).border({width:2,color:#BFDBFE,style:BorderStyle.Dashed}).borderRadius(8).clip(true).animation({duration:300,curve:Curve.EaseInOut})}.layoutWeight(1).height(100%).padding(24).alignItems(HorizontalAlign.Start).backgroundColor(#F9FAFB)}.width(100%).height(100%)}}动画让切换效果更直观在演示页里切换对齐方式时加.animation({ duration: 250 })非常有价值——用户能看到子项从原来的位置滑动到新位置而不是突然跳变。这比截图对比更能理解对齐的含义。Column({space:this.columnSpace}){// 子项}.alignItems(this.alignItems).justifyContent(this.justifyContent).animation({duration:250,curve:Curve.EaseInOut})只需要在被动画的属性所在的组件上加.animation()ArkUI 会自动对属性变化做插值过渡。代码生成面板演示页最实用的功能之一是实时显示当前配置对应的代码。用模板字符串拼接Text([Column({ space:${this.columnSpace}}) {,//${this.demoChildCount}个子项,},.alignItems(HorizontalAlign.${currentAlignName}),.justifyContent(FlexAlign.${currentJustifyName}),].join(\n))用户看到效果满意直接把右下角的代码复制到项目里。这个功能很小但体验提升明显。写在最后交互式演示页的价值不只在于教学在实际开发中也很有用——拿不准某个对齐属性的效果时改一行状态、看一眼预览比反复查文档高效多了。这种状态驱动样式的模式在 ArkUI 里非常自然做一个布局探索工具只需要几十行状态管理代码。