找回密码
 立即注册
首页 业界区 安全 鸿蒙应用开发UI基础第八节: ArkTS声明式UI与页面基础结 ...

鸿蒙应用开发UI基础第八节: ArkTS声明式UI与页面基础结构

翁真如 3 天前
【学习目标】


  • 理解声明式UI与命令式UI的核心差异,建立“数据驱动视图”的核心认知;
  • 熟练掌握ArkTS页面核心结构(@Entry/@Component + build函数),能独立搭建标准页面骨架;
  • 掌握vp/fp/px/lpx等尺寸单位的适用场景与适配原则,熟练使用系统原生尺寸转换方法,养成规范的单位使用习惯;
  • 学会使用@State状态变量,实现“数据修改→UI自动刷新”的基础交互;
  • 能快速定位并解决多根组件、UI不刷新、尺寸适配等新手高频页面问题。
【课前铺垫】

从本节开始,我们正式进入ArkTS UI开发的核心阶段:声明式UI开发。这是鸿蒙官方推荐的主流开发方式,也是让应用“看得见、可交互”的关键。本节将围绕“页面基础结构”展开,从页面核心组成、多设备尺寸适配,到数据驱动UI刷新,全方位解析声明式UI的核心逻辑,学好这些内容能为后续组件、布局、状态管理的学习筑牢基础。
先思考几个核心问题,带着问题学习更高效:

  • 为什么鸿蒙优先推荐声明式UI?它比传统命令式UI更高效的核心原因是什么?
  • 一个可运行的ArkTS页面,哪些结构是缺一不可的?少了会导致什么问题?
  • vp和fp是什么,有什么区别?为什么同样的尺寸在平板和手机上显示效果不同?
  • 修改变量后UI不刷新,大概率是哪里出了问题?
  • 特殊场景需要px与vp/fp互转时,如何使用系统原生方法实现?
一、工程结构

本节创建新工程AppDemo(基于鸿蒙API 12+/Stage模型),聚焦讲解UI页面的基本组成和声明式UI,工程核心目录结构如下:
  1. AppDemo
  2. ├── AppScope                 # 应用全局配置目录
  3. │   └── app.json5            # 全局配置(包名/应用名称/图标/版本等)
  4. ├── entry                    # 主模块目录(Entry HAP,应用核心代码包)
  5. │   ├── src/main
  6. │   │   ├── ets              # ArkTS代码核心目录
  7. │   │   │   ├── common       # 新增:通用工具/常量目录
  8. │   │   │   │   └── constants  
  9. │   │   │   │       └── LayoutConstants.ets # 通用布局常量
  10. │   │   │   ├── entryability # UIAbility组件目录
  11. │   │   │   └── pages        # Page页面目录
  12. │   │   │       └── Index.ets    # 工程默认首页
  13. │   │   ├── resources        # 资源文件目录(字体/文字/数值等放这里)
  14. │   │   │   └── base
  15. │   │   │       ├── element
  16. │   │   │       │   ├── float.json # 字体大小(fp)/数值型资源
  17. │   │   │       │   └── string.json # 字符串型资源(页面文字/提示语等)
  18. │   │   │       └── profile
  19. │   │   └── module.json5     # 模块配置文件
  20. └── 其他目录(构建/测试/配置相关)
复制代码
二、声明式UI与命令式UI核心差异

两种UI开发模式的本质区别在于关注焦点UI更新逻辑,通过对比能更清晰理解声明式UI的优势:
1. 核心思维


  • 命令式UI:关注“怎么做”,需要开发者编写每一步操作指令(创建组件→设置属性→添加事件→手动更新),全程掌控UI的绘制和修改。
  • 声明式UI:关注“是什么”,只需描述UI的最终状态和数据关联关系,框架自动处理渲染、更新逻辑,数据变化时UI自动刷新。
2. 代码对比(实现“点击按钮变色”)

命令式UI(iOS原生Swift)
  1. import UIKit
  2. class ViewController: UIViewController {
  3.     var btn: UIButton!
  4.    
  5.     override func viewDidLoad() {
  6.         super.viewDidLoad()
  7.         
  8.         btn = UIButton(type: .system)
  9.         btn.setTitle("点击变色", for: .normal)
  10.         btn.backgroundColor = UIColor(red: 0/255, green: 125/255, blue: 255/255, alpha: 1)
  11.         btn.setTitleColor(.white, for: .normal)
  12.         btn.frame = CGRect(x: 100, y: 200, width: 200, height: 80)
  13.         btn.addTarget(self, action: #selector(btnClick), for: .touchUpInside)
  14.         
  15.         self.view.addSubview(btn)
  16.     }
  17.    
  18.     @objc func btnClick() {
  19.         btn.backgroundColor = UIColor(red: 255/255, green: 103/255, blue: 0/255, alpha: 1)
  20.     }
  21. }
复制代码
声明式UI(ArkTS)
  1. // 引入通用布局常量
  2. import { LayoutConstants } from '../common/constants/LayoutConstants';
  3. // 声明页面入口
  4. @Entry
  5. // 声明组件
  6. @Component
  7. struct Index {
  8.     // 状态变量:被@State修饰,数据与UI绑定,修改后自动刷新
  9.     @State btnColor: string = "#007dff";
  10.    
  11.     // build()内定义组件层级和属性
  12.     build() {
  13.         Column({ space: 20 }) {
  14.             Button("点击变色")
  15.                 .backgroundColor(this.btnColor)
  16.                 .width(200)
  17.                 .height(80)
  18.                 .fontSize($r('app.float.font_size_normal'))
  19.                 .onClick(() => {
  20.                     // 点击修改颜色
  21.                     this.btnColor = "#ff6700";
  22.                 });
  23.         }
  24.         .width(LayoutConstants.FULL_WIDTH) // 设置根组件宽度
  25.         .height(LayoutConstants.FULL_HEIGHT) // 设置根组件高度
  26.         .justifyContent(FlexAlign.Center);
  27.     }
  28. }
复制代码
三、ArkTS页面核心结构

一个可运行的ArkTS页面,必须包含入口标记、组件标记、结构体、UI构建函数四大核心部分,缺一不可。
1. 核心组成详解

组成部分作用说明核心禁忌@Entry页面入口标识,告知系统该组件可作为独立页面加载一个文件仅能有一个@Entry,否则编译报错@Component自定义组件标识,所有UI组件(页面级/子组件级)都需添加无此标识,结构体无法作为UI组件使用,编译直接报错structArkTS组件的核心载体,基于结构体实现,无需继承类命名需大驼峰(如HomePage),小写开头会报语法警告build()函数唯一的UI构建函数,仅负责描述UI结构1. 禁止编写业务逻辑(console.log/网络请求等);2. 必须只有一个根组件2. 核心禁忌(新手必避)


  • ❌ 禁止一个文件多个@Entry:编译直接报错,需拆分组件或页面。
  • ❌ 禁止build()函数多根组件:多根组件报错In an '@Entry' decorated component, the 'build' method can have only one root node, which must be a container component. 。
  • ❌ 禁止在build()函数写非UI逻辑:如console.log、网络请求等,需封装到独立方法中,在事件回调(如onClick)或生命周期中调用。
四、多设备尺寸适配

4.1 为什么需要尺寸适配?—— 屏幕底层逻辑

(1)屏幕尺寸的核心定义

我们常说的6.7英寸手机 11英寸平板,这里的“英寸”是屏幕 发光区域对角线的物理长度
1.png


  • 单位换算:1英寸 = 2.54厘米
  • 计算公式:$\text{对角线长度} = \sqrt{\text{屏幕宽度}^2 + \text{屏幕高度}^2}$
(2)影响显示效果的三大核心概念


  • 物理尺寸:屏幕实际大小(英寸),决定设备握持感;
  • 分辨率:屏幕横向和纵向的物理像素总数(px),如1080×2340,代表屏幕“精细度”;
  • 像素密度(PPI):每英寸屏幕包含的物理像素数,是影响显示效果的核心参数。PPI数值越高,像素点越密集,屏幕显示越细腻(手机PPI≈400+,平板PPI≈200+)。
(3)适配的核心痛点

px(物理像素)是屏幕硬件的最小显示单元,直接与屏幕PPI绑定,导致相同px值在不同设备上视觉大小差异极大:

  • 100px按钮在普通手机(中等PPI)上大小适中;
  • 在高清屏上,因像素点更密,100px按钮视觉上极小;
  • 在低像素平板上,因像素点更疏,100px按钮视觉上极大。
为解决这种差异,让开发者无需手动换算不同设备的像素,同时满足“视觉大小一致+无障碍适配+多语言适配”需求,鸿蒙引入了vp/fp单位。使用虚拟像素,使元素在不同密度的设备上具有一致的视觉大小。
2.png

4.2 鸿蒙核心尺寸单位(按使用优先级排序)

单位完整名称正确定义特性适用场景vp虚拟像素(virtual pixel)系统根据屏幕密度自动换算的逻辑单位,保证视觉大小一致。多设备上显示大小一致,与物理像素无关宽、高、间距、边距等布局fp字体像素专门用于字体的单位,在 vp 基础上支持系统字体大小缩放。跟随系统字体大小设置,支持无障碍所有文字的 fontSizepx物理像素屏幕硬件最小显示单元,直接对应物理像素点。不同设备上显示大小差异极大,无自适配Canvas、像素级精准绘制lpx逻辑像素(旧)按设计稿宽度(默认720)等比缩放的单位。无密度适配、无字体缩放,API9+已淘汰仅老项目兼容,新项目禁止使用4.3 系统原生尺寸转换方法

鸿蒙系统在UIContext中内置了以下尺寸转换方法,适配当前UI实例所在屏幕的虚拟像素比例:

  • vp2px(value: number): number:将vp单位值转换为px单位值;
  • px2vp(value: number): number:将px单位值转换为vp单位值;
  • fp2px(value: number): number:将fp单位值转换为px单位值;
调用规则:需通过当前组件的this.getUIContext()获取上下文后调用,且建议增加空值安全处理:
  1. const pxValue = this.getUIContext()?.vp2px(100) ?? 100;
复制代码
注意:getUIContext() 禁止在 build() 中直接调用,应在 onClick、生命周期等可获取完整上下文的位置使用。
4.4 核心配置文件

(1)全局通用布局常量

路径:entry/src/main/ets/common/constants/LayoutConstants.ets
  1. export class LayoutConstants {
  2.     public static readonly FULL_WIDTH: string = '100%';
  3.     public static readonly HALF_WIDTH: string = '50%';
  4.     public static readonly FULL_HEIGHT: string = '100%';
  5.     public static readonly BASE_PADDING: number = 24;
  6.     public static readonly BASE_SPACE: number = 16;
  7. }
复制代码
(2)字体大小资源文件(float.json)

路径:entry/src/main/resources/base/element/float.json
  1. {
  2.   "float": [
  3.     {
  4.       "name": "font_size_normal",
  5.       "value": "20fp"
  6.     },
  7.     {
  8.       "name": "font_size_medium",
  9.       "value": "24fp"
  10.     },
  11.     {
  12.       "name": "font_size_large",
  13.       "value": "30fp"
  14.     },
  15.     {
  16.       "name": "font_size_counter",
  17.       "value": "26fp"
  18.     },
  19.     {
  20.       "name": "button_size_width",
  21.       "value": "200vp"
  22.     }
  23.   ]
  24. }
复制代码
(3)文字资源文件(string.json)

路径:entry/src/main/resources/base/element/string.json
  1. {
  2.   "string": [
  3.     {
  4.       "name": "page_title_main",
  5.       "value": "从页面结构、尺寸适配到数据驱动的全方位解析"
  6.     },
  7.     {
  8.       "name": "page_title_home",
  9.       "value": "我的首页"
  10.     },
  11.     {
  12.       "name": "btn_click_change",
  13.       "value": "点击变色"
  14.     },
  15.     {
  16.       "name": "text_counter",
  17.       "value": "当前计数:"
  18.     },
  19.     {
  20.       "name": "btn_change_color",
  21.       "value": "点击按钮修改颜色"
  22.     }
  23.   ]
  24. }
复制代码
4.5 适配黄金法则


  • 通用布局抽常量:100%/50%等全项目通用百分比,抽离到LayoutConstants;
  • 字体大小抽离:所有fontSize必须通过$r('app.float.xxx')引用float.json,不推荐硬编码;
  • 页面文字抽离:所有页面/按钮/提示文字通过$r('app.string.xxx')引用string.json,不推荐硬编码;
  • 单位使用规范:布局优先用vp/百分比,文字必须用fp(支持无障碍缩放);仅Canvas可用px,新项目禁用lpx;
  • 转换慎使用:特殊场景需转换时,仅使用系统原生UIContext方法,不推荐自定义换算。
五、核心实战:@State与数据驱动UI(完整示例)
  1. import { LayoutConstants } from '../common/constants/LayoutConstants';
  2. @Entry
  3. @Component
  4. struct Index {
  5.   // @State 基础状态装饰器:修改后自动驱动UI刷新
  6.   @State btnColor: string = "#007dff";
  7.   @State count: number = 0;
  8.   build() {
  9.     Column({ space: LayoutConstants.BASE_SPACE }) {
  10.       Text($r('app.string.page_title_main'))
  11.         .fontSize($r('app.float.font_size_medium'))
  12.       // 按钮颜色由状态变量控制
  13.       Button($r('app.string.btn_click_change'))
  14.         .backgroundColor(this.btnColor)
  15.         .width($r('app.float.button_size_width'))
  16.         .height(50) // 硬编码高度 默认单位vp 如果使用字符串需添加单位("50vp")不推荐
  17.         .fontSize($r('app.float.font_size_normal'))
  18.         .onClick(() => {
  19.           this.btnColor = "#ff6700";
  20.         });
  21.       // 计数器展示
  22.       Text() {
  23.         Span($r('app.string.text_counter'))
  24.         Span(`${this.count}`)
  25.       }
  26.       .fontSize($r('app.float.font_size_counter'));
  27.       // 计数器按钮组
  28.       Row({ space: 16 }) {
  29.         Button("计数增加")
  30.           .onClick(() => {
  31.             this.count++;
  32.           });
  33.         Button("计数减少")
  34.           .onClick(() => {
  35.             this.count--;
  36.           });
  37.       }
  38.     }
  39.     .width(LayoutConstants.FULL_WIDTH)
  40.     .height(LayoutConstants.FULL_HEIGHT)
  41.     .justifyContent(FlexAlign.Center);
  42.   }
  43. }
复制代码
【新手排查】UI不刷新常见原因:普通变量仅能设置初始值,修改后无法驱动UI刷新;需为动态变量添加 @State 装饰器才能实现“数据变化→UI更新”。
六、内容总结


  • 核心语法规则

    • ArkTS页面必须包含@Entry+@Component+struct+build()四大核心部分;
    • build()函数仅描述UI,禁止编写业务逻辑,且只能有一个根组件;
    • 多根组件报错示例:
    1. build() {
    2.     Text("标题");
    3.     Button("按钮");
    4. }
    复制代码
    解决方案:使用Column/Row/Stack等容器组件包裹所有子组件,确保仅有一个根组件:
    1. build() {
    2.     Column() {
    3.         Text($r('app.string.page_title_home'));
    4.         Button($r('app.string.btn_change_color'));
    5.     }
    6. }
    复制代码
  • 资源管理规范

    • 通用百分比抽离到LayoutConstants,字体大小在float.json管理,所有页面文字在string.json管理,不推荐硬编码;
    • 布局优先用vp/百分比,文字大小必须用fp,不推荐px/lpx;

七、代码仓库


  • 调用方工程:AppDemo
  • 仓库地址:https://gitee.com/HarmonyOS-UI-Basics/harmony-os-ui-basics.git
八、下节预告

本节我们掌握了页面的基本组成、数据驱动逻辑、资源引用以及屏幕尺寸适配概念,下一节将学习线性布局容器(Column/Row),掌握主轴、交叉轴的对齐规则,学会像搭积木一样排列UI组件,实现登录页、首页等常见页面的布局开发。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册