深绿与暖白的家居氛围——HarmonyOS ArkUI 智能家居六房间场景交互设计实战
一、技术前言
在物联网与消费电子深度融合的今天,智能家居已从概念走向普及。从单一的智能灯泡到全屋智能系统,用户对智能家居应用的交互体验提出了越来越高的要求。HarmonyOS 作为面向全场景的分布式操作系统,其声明式 UI 框架 ArkUI 为开发者提供了一套高效、简洁的跨设备 UI 开发方案。
ArkUI 是 HarmonyOS 提供的一套声明式 UI 开发框架,它采用 TypeScript/ArkTS 作为开发语言,通过声明式的语法来描述 UI 界面。与传统的命令式 UI 框架不同,ArkUI 的核心思想是"描述 UI 状态,框架自动渲染"。开发者只需要声明 UI 在不同状态下的样子,框架会自动处理状态变化时的界面更新。这种开发模式大幅提升了开发效率,同时也让代码结构更加清晰、易于维护。
ArkUI 的核心特性包括以下几个方面。首先是声明式语法,通过 @Component 装饰器标记组件结构体,build() 方法内以声明式的方式描述 UI 布局,代码即界面,所见即所得。其次是响应式状态管理,提供了 @State、@Observed、@ObjectLink 等多种状态装饰器,能够精准追踪数据变化并触发 UI 更新,实现数据与视图的双向绑定。第三是内置组件丰富,框架内置了 Text、Button、Image、List、Grid、Toggle、Slider 等数十种基础组件和容器组件,满足绝大多数界面开发需求。第四是动画系统完善,支持属性动画、显式动画、转场动画等多种动画形式,通过 animateTo 等 API 可以轻松实现流畅的过渡效果。最后是跨设备能力,依托 HarmonyOS 的分布式软总线,ArkUI 应用可以轻松实现多设备协同、跨端流转等特性。
本文将以一款名为"智家"的智能家居应用为载体,深入剖析一个完整的 ArkUI 页面实现。该应用涵盖六个功能模块——首页设备宫格、客厅设备列表开关、卧室场景模式卡、厨房能耗统计卡、安防监控卡片以及我的家庭管理卡,每个模块都采用了完全不同的布局风格和交互方式。我们将逐段分析代码,从色彩体系设计到数据模型构建,从组件主体结构到每个 Tab 的详细实现,全面展现 ArkUI 在智能家居场景下的应用实践。
这款应用采用深色主题配合深绿(#059669)与暖白(#F5DEB3)的配色方案,营造出温馨而不失科技感的家居氛围。深绿色象征着自然与生机,暖白色则传递出温暖与舒适,两者在深色背景的衬托下形成了鲜明而和谐的视觉对比。接下来,让我们一同走进这个充满设计感的智能家居世界。





二、整体架构流程图
在深入代码细节之前,我们先通过架构流程图来了解该页面的整体结构和各模块之间的关系。这有助于我们建立一个全局的认知框架,为后续的逐段分析打下基础。
从架构图中可以清晰地看到,该页面采用了典型的分层设计思想。最底层是基础资源层,包含色彩体系、元数据常量和辅助函数,为整个页面提供基础支撑。往上是数据模型层,六个 @Observed 装饰的类分别对应不同业务领域的数据结构,每个模型都有自己的初始数据集。
中间的组件主体层是整个页面的核心,@Component Page806 结构体承载了所有的状态变量、生命周期函数和 UI 构建逻辑。它向下依赖基础资源层和数据模型层,向上组织头部区域、内容区域、公共模块和弹窗系统。
内容区域是页面最丰富的部分,六个 Tab 每个都有完全不同的布局和交互方式——首页是设备宫格布局,客厅是列表开关布局,卧室是横向滚动的场景卡片,厨房是能耗统计卡片,安防是监控卡片列表,我的是个人中心式的信息卡片。这种多形态的 Tab 设计展示了 ArkUI 在复杂页面构建上的灵活性。
公共模块中的能耗图表和底部 Tab 栏则是跨 Tab 共享的组件,图表卡片在所有 Tab 下方都有展示,而底部 Tab 栏负责页面切换导航。弹窗系统则采用了统一的遮罩层设计,三套弹窗(添加、编辑、删除)复用相同的遮罩逻辑,体现了良好的代码复用思想。
三、色彩体系设计
色彩是 UI 设计的灵魂。一个好的色彩体系不仅能提升视觉美感,还能有效传递信息层级、引导用户注意力。在智能家居场景中,色彩的选择尤为重要——它需要同时满足科技感和温馨感这两种看似矛盾的需求。
该源码的色彩体系通过 ColorPalette 接口进行类型定义,然后以 COLORS 常量对象的形式统一管理所有颜色值。这种接口+常量的模式是 ArkUI/TypeScript 项目中的最佳实践之一,它既保证了类型安全,又提供了良好的可维护性。
interface ColorPalette {
bg: string; card: string; chip: string;
title: string; sub: string; text3: string;
green: string; greenD: string;
warm: string; warmD: string;
blue: string; orange: string;
line: string; tabOn: string; mask: string;
}
这段接口定义了 17 个颜色属性,按照功能可以分为五大类。第一类是背景色系列:bg(页面背景)、card(卡片背景)、chip(胶囊/标签背景),形成了由深到浅的三层背景结构,营造出空间层次感。第二类是文字色系列:title(主标题色)、sub(次要文字色)、text3(辅助文字色),三级文字色构成了清晰的信息层级。第三类是主题色系列:green(主绿)和 greenD(深绿),作为整个应用的主色调,用于强调、激活态和正向反馈。第四类是点缀色系列:warm(暖白)、warmD(深暖)、blue(蓝色)、orange(橙色),分别用于数值高亮、链接信息和警告提示。第五类是功能色系列:line(分割线)、tabOn(Tab 激活色)、mask(遮罩层),服务于特定的 UI 元素。
const COLORS: ColorPalette = {
bg: '#0F1419', card: '#1A2332', chip: '#1E3A2E',
title: '#F5F0E8', sub: '#8B9A8F', text3: '#5A6B5E',
green: '#059669', greenD: '#047857',
warm: '#F5DEB3', warmD: '#D4B896',
blue: '#3B82F6', orange: '#F97316',
line: '#2A3A4A', tabOn: '#059669',
mask: 'rgba(0,0,0,0.65)',
};
具体来看颜色值的选取。背景色 bg 采用 #0F1419,这是一种偏蓝调的深黑色,比纯黑(#000000)更柔和,长时间观看也不易疲劳。卡片背景 card 是 #1A2332,比背景色亮约 15%,形成微妙的浮起效果。胶囊背景 chip 是 #1E3A2E,带明显的绿色调,用于选中态、标签和轻量级按钮背景。
文字色的设计同样考究。主标题色 title 是 #F5F0E8,这是一种偏暖的米白色,而非冷白色,与整体的暖色调主题相呼应。次要文字色 sub 是 #8B9A8F,带一点绿色调的灰,在深色背景上有良好的可读性又不会过于刺眼。辅助文字色 text3 是 #5A6B5E,用于最不重要的信息,如时间戳、单位等。
主题色 green 选用了 #059669(祖母绿),这是一种饱和度适中的深绿色,既有自然的生机感,又足够沉稳,非常适合智能家居这种追求品质感的场景。greenD 深绿 #047857 则用于 hover 态、边框等需要更深一点绿色的场景。暖白色 warm 的 #F5DEB3(小麦色)是整个配色的点睛之笔,它为冷色调的深色界面注入了温暖的家居气息,主要用于数值高亮和重点信息展示。
点缀色中,蓝色 #3B82F6 是标准的科技蓝,用于链接和信息类提示;橙色 #F97316 是明亮的警告橙,用于告警状态和危险操作(如删除按钮)。
这种色彩体系的设计哲学是"克制而丰富"——主色调只有深绿和暖白两种,但通过明暗变化、透明度调整和少量点缀色的搭配,构建出了层次分明、功能完备的色彩系统。所有颜色都通过统一的 COLORS 对象引用,一旦需要调整主题色,只需要修改一处即可全局生效,维护成本极低。
四、Tab元数据与辅助数据
在任何一个多 Tab 的应用中,Tab 的配置数据都是基础中的基础。该源码将所有的 Tab 配置、房间列表、月份数据等辅助信息都以常量数组的形式集中定义,遵循了"数据驱动视图"的设计原则。
interface TabMeta { icon: string; label: string; }
const TAB_LIST: TabMeta[] = [
{ icon: '🏠', label: '首页' },
{ icon: '🛋️', label: '客厅' },
{ icon: '🛏️', label: '卧室' },
{ icon: '🍳', label: '厨房' },
{ icon: '🔒', label: '安防' },
{ icon: '👤', label: '我的' },
];
TabMeta 接口定义了每个 Tab 的数据结构,包含 icon(图标)和 label(文字标签)两个字段。TAB_LIST 数组则按顺序存储了六个 Tab 的配置数据。这里使用了 emoji 作为图标,这是一种轻量级的图标方案——不需要引入额外的图标库,不需要处理图片资源,直接使用 Unicode 字符即可,在 HarmonyOS 系统字体的支持下,emoji 图标渲染清晰、风格统一。
六个 Tab 的选择也很有代表性:首页是全局设备总览,客厅和卧室是家庭中最主要的两个活动空间,厨房是能耗大户需要重点关注,安防是智能家居的安全基础,我的则是个人与家庭管理中心。这种空间+功能的混合分类方式,符合用户对智能家居的认知习惯。
const ROOM_TABS: string[] = ['全部', '客厅', '卧室', '厨房', '书房', '阳台'];
ROOM_TABS 是房间筛选标签数组,用于首页顶部的房间切换。六个选项从"全部"到五个具体房间,用户可以按房间维度筛选设备列表。这种二级分类(一级大 Tab + 二级房间筛选)的设计,在设备数量较多时能有效降低信息密度,提升查找效率。
const MONTH_IDX: number[] = [0, 1, 2, 3, 4, 5];
const MONTH_NAME: string[] = ['03月', '04月', '05月', '06月', '07月', '08月'];
const ENERGY_VAL: number[] = [320, 285, 356, 412, 468, 395];
这三组常量服务于能耗趋势图表。MONTH_IDX 是月份的索引数组,用于 ForEach 循环遍历;MONTH_NAME 存储了六个月的显示名称;ENERGY_VAL 则是对应的能耗数值。将索引和数据分离的写法,在 ForEach 中可以同时访问到索引和对应的值,实现方式简洁高效。数据呈现出明显的季节性特征——从三月到七月持续上升,七月达到峰值 468(夏季空调用电高峰),八月略有回落至 395,非常符合实际生活中的用电规律。
const DEVICE_ROW0: number[] = [0, 1];
const DEVICE_ROW1: number[] = [2, 3];
const DEVICE_ROW2: number[] = [4, 5];
const ENERGY_ROW0: number[] = [0, 1];
const ENERGY_ROW1: number[] = [2, 3];
这几组行索引常量是一个值得关注的设计细节。在 ArkUI 中,要实现两列网格布局有多种方式——可以用 Grid 组件,可以用 Flex 换行,也可以手动分行。该源码选择了"手动分行"的方式,将设备列表按每两个一组拆分成三行(DEVICE_ROW0/1/2),能耗卡片拆分成两行(ENERGY_ROW0/1)。
这种方式的优势在于:第一,布局完全可控,每行的元素数量和间距可以精确调整;第二,不需要依赖 Grid 组件,在某些特定场景下性能更优;第三,代码结构清晰,每一行都是独立的 Row 容器,便于针对特定行添加特殊样式。当然,这种方式也有局限性——当数据量动态变化时,手动分行就不太适用了。但对于设备数量固定的演示型应用来说,这是一种简洁有效的实现方式。
const ENERGY_RANK: string[] = ['空调 42%', '热水器 23%', '冰箱 15%', '照明 10%', '其他 10%'];
ENERGY_RANK 是设备能耗排名数据,采用"名称+百分比"的字符串格式,共五项。空调以 42% 的占比高居榜首,符合夏季用电的实际情况。这些数据在厨房 Tab 的能耗排名卡片中通过进度条的形式可视化展示,前三名用主题绿色高亮,后两名用灰色显示,形成清晰的视觉层级。
这些辅助数据虽然看起来简单,但它们是整个页面的数据基石。将所有配置数据集中定义、统一管理,是构建可维护 UI 的第一步。当需要修改 Tab 名称、增加房间、调整图表数据时,只需要找到对应的常量数组进行修改即可,不需要在 UI 代码中四处搜索硬编码的字符串。
五、工具函数
在色彩体系和元数据之上,该源码还定义了两个小巧但实用的辅助函数:statusColor 和 deviceColor。这两个函数承担着"状态到颜色"的映射职责,是连接数据状态与视觉表现的桥梁。
function statusColor(s: string): string {
if (s === '在线') return COLORS.green;
if (s === '离线') return COLORS.text3;
if (s === '告警') return COLORS.orange;
return COLORS.text3;
}
statusColor 函数接收一个状态字符串,返回对应的颜色值。它处理三种状态:"在线"返回绿色(正常)、"离线"返回辅助灰色(不可用)、"告警"返回橙色(异常警告)。如果传入的状态不在这三种之中,则默认返回灰色。
这个函数的设计体现了"状态可视化"的设计思想。在安防监控等场景中,设备的在线/离线/告警状态是用户最关心的信息之一,通过颜色进行直观区分,用户只需扫一眼就能掌握所有设备的状态。绿色代表安全正常,橙色代表需要关注,灰色代表不可用,这种色彩语义与用户的普遍认知完全一致。
function deviceColor(on: boolean): string {
return on ? COLORS.green : COLORS.text3;
}
deviceColor 函数更加简单,接收一个布尔值表示设备开关状态,开启返回绿色,关闭返回灰色。这个函数虽然只有一行代码,但它将"设备开关状态→颜色"的映射逻辑集中封装,避免了在多处 UI 代码中重复编写三元表达式。
这两个函数虽然体量很小,但它们代表了一种重要的编码思想——将业务规则从 UI 代码中抽离出来。想象一下,如果没有这些辅助函数,我们需要在每个显示状态颜色的地方都写一遍条件判断,代码会变得冗余且难以维护。一旦产品经理说"告警状态的颜色从橙色改成红色",我们需要找到所有用到这个判断的地方逐一修改,很容易遗漏。而有了辅助函数,只需要修改函数内部的一处实现即可全局生效。
从架构设计的角度看,这两个函数属于"展示层工具函数"的范畴。它们不处理业务逻辑,只负责将业务状态转换为展示属性(颜色)。这种职责单一的小工具函数,是构建高质量 UI 代码的重要组成部分。
六、数据模型层
数据模型是应用的骨架。在 ArkUI 中,通过 @Observed 装饰器可以将一个普通类标记为可观察对象,当对象的属性发生变化时,依赖该属性的 UI 会自动刷新。这是 ArkUI 响应式编程的核心机制之一。该源码定义了六个数据模型类,每个类对应一个业务领域,我们逐一进行分析。
6.1 DeviceItem——设备数据模型
@Observed export class DeviceItem {
name: string; icon: string; status: string; value: string; room: string;
constructor(name: string, icon: string, status: string, value: string, room: string) {
this.name = name; this.icon = icon; this.status = status; this.value = value; this.room = room;
}
}
DeviceItem 是首页设备宫格的数据模型,包含五个属性:name(设备名称)、icon(设备图标)、status(开关状态,值为"开"或"关")、value(设备当前值,如温度、亮度等)、room(所属房间)。这个模型的设计非常典型——每个设备都有身份标识(名称+图标)、状态信息(开/关)、运行数值和空间归属四个维度的信息。
const DEVICE_LIST: DeviceItem[] = [
new DeviceItem('智能空调', '❄️', '开', '26°C', '客厅'),
new DeviceItem('智能灯光', '💡', '开', '80%', '客厅'),
new DeviceItem('空气净化器', '🌬️', '关', '—', '卧室'),
new DeviceItem('智能窗帘', '🪟', '开', '75%', '卧室'),
new DeviceItem('智能电视', '📺', '关', '—', '客厅'),
new DeviceItem('扫地机器人', '🤖', '开', '充电中', '全屋'),
];
初始数据中包含了六个设备,覆盖了客厅和卧室的主要家电品类。注意观察细节:关闭状态的设备 value 字段用"—“占位,而不是显示具体数值,这是一个很好的 UX 细节;扫地机器人的房间属性是"全屋”,体现了移动设备的特殊性。
6.2 LivingItem——客厅设备模型
@Observed export class LivingItem {
name: string; icon: string; isOn: boolean; status: string;
constructor(name: string, icon: string, isOn: boolean, status: string) {
this.name = name; this.icon = icon; this.isOn = isOn; this.status = status;
}
}
LivingItem 是客厅设备列表的数据模型。与 DeviceItem 不同的是,它使用布尔类型的 isOn 字段来表示开关状态,而不是字符串。这种设计更适合与 Toggle 开关组件配合使用——Toggle 的 isOn 属性本身就是布尔值,可以直接双向绑定。status 字段则用于显示更详细的状态描述,如"暖光 80%"、"制冷 26°C"等。
const LIVING_LIST: LivingItem[] = [
new LivingItem('主灯', '💡', true, '暖光 80%'),
new LivingItem('空调', '❄️', true, '制冷 26°C'),
new LivingItem('电视', '📺', false, '待机'),
new LivingItem('音响', '🔊', true, '播放中'),
new LivingItem('加湿器', '💧', false, '水箱空'),
new LivingItem('扫地机', '🤖', true, '清扫中 65%'),
];
六个客厅设备各具特色,状态描述也比首页设备更加详细。值得注意的是加湿器的状态"水箱空"——这是一个异常状态的例子,说明设备不仅有开/关两种状态,还可能有需要用户关注的异常状态。
6.3 SceneItem——场景模式模型
@Observed export class SceneItem {
name: string; icon: string; desc: string; active: boolean;
constructor(name: string, icon: string, desc: string, active: boolean) {
this.name = name; this.icon = icon; this.desc = desc; this.active = active;
}
}
SceneItem 是卧室场景模式的数据模型。场景模式是智能家居的核心概念之一——一键触发一系列设备的预设状态。这个模型包含场景名称、图标、描述文本和激活状态四个属性。active 布尔字段标识当前哪个场景处于激活状态,在 UI 上表现为高亮的激活态样式。
const SCENE_LIST: SceneItem[] = [
new SceneItem('睡眠模式', '🌙', '关灯·关窗帘·空调28°C', false),
new SceneItem('阅读模式', '📖', '台灯开·暖光·轻音乐', false),
new SceneItem('观影模式', '🎬', '关主灯·窗帘关闭·投影开', true),
new SceneItem('离家模式', '🚪', '全关·布防·启动监控', false),
new SceneItem('回家模式', '🏠', '开灯·空调启动·播放音乐', false),
new SceneItem('用餐模式', '🍽️', '餐厅灯开·轻音乐·关电视', false),
];
六个场景覆盖了日常生活的主要场景:睡眠、阅读、观影、离家、回家、用餐。每个场景的描述文本用"·“分隔列出了该场景下的设备动作,让用户一眼就能理解这个场景会做什么。初始激活的是"观影模式”,这是一个很有代入感的场景设定。
6.4 EnergyItem——能耗数据模型
@Observed export class EnergyItem {
label: string; value: string; unit: string; trend: string; icon: string;
constructor(label: string, value: string, unit: string, trend: string, icon: string) {
this.label = label; this.value = value; this.unit = unit; this.trend = trend; this.icon = icon;
}
}
EnergyItem 是厨房能耗统计的数据模型。这个模型的设计比较丰富,包含五个字段:label(项目名称)、value(数值)、unit(单位)、trend(趋势,含箭头方向和百分比)、icon(图标)。将数值和单位拆分成两个字段,便于在 UI 上设置不同的字号和颜色——数值大而醒目,单位小而低调,这是数据卡片的经典排版手法。
const ENERGY_LIST: EnergyItem[] = [
new EnergyItem('今日用电', '8.6', 'kWh', '↓ 12%', '⚡'),
new EnergyItem('今日用气', '0.42', 'm³', '↑ 5%', '🔥'),
new EnergyItem('今日用水', '128', 'L', '↓ 8%', '💧'),
new EnergyItem('本月总计', '256', '元', '↑ 3.2%', '📊'),
];
四个能耗指标涵盖了家庭的主要资源消耗:电、气、水和费用总计。趋势字段的箭头方向直接编码了涨跌信息——向下箭头(↓)表示同比下降(好事,显示绿色),向上箭头(↑)表示同比上升(需关注,显示橙色)。这种数据设计与 UI 展示的配合非常紧密。
6.5 SecurityItem——安防设备模型
@Observed export class SecurityItem {
location: string; status: string; icon: string; lastActive: string;
constructor(location: string, status: string, icon: string, lastActive: string) {
this.location = location; this.status = status; this.icon = icon; this.lastActive = lastActive;
}
}
SecurityItem 是安防监控的数据模型。与设备模型不同,安防设备更关注"位置"和"最近活动时间"。location 字段标识设备安装位置(如"前门摄像头"),status 字段有三种可能值(在线/离线/告警),lastActive 记录了上次活动的时间(如"2分钟前")。
const SECURITY_LIST: SecurityItem[] = [
new SecurityItem('前门摄像头', '在线', '📹', '2分钟前'),
new SecurityItem('后门摄像头', '在线', '📹', '5分钟前'),
new SecurityItem('客厅摄像头', '离线', '📹', '1小时前'),
new SecurityItem('车库门禁', '在线', '🚪', '刚刚'),
new SecurityItem('烟雾报警器', '在线', '🚨', '30分钟前'),
new SecurityItem('窗户传感器', '告警', '🪟', '10分钟前'),
];
六个安防设备包含了摄像头、门禁、烟雾报警器和窗户传感器四种类型,覆盖了家庭安防的主要维度。状态分布也很有代表性——四个在线、一个离线、一个告警,展示了 statusColor 函数所有可能的输出场景。窗户传感器的"告警"状态尤为重要,它会触发额外的"处理"按钮,体现了告警状态下的特殊交互逻辑。
6.6 FamilyItem——家庭成员模型
@Observed export class FamilyItem {
name: string; role: string; avatar: string; online: boolean; homeName: string;
constructor(name: string, role: string, avatar: string, online: boolean, homeName: string) {
this.name = name; this.role = role; this.avatar = avatar; this.online = online; this.homeName = homeName;
}
}
FamilyItem 是家庭成员的数据模型,用于"我的"页面中的家庭成员列表。包含姓名、角色(户主/成员)、头像、在线状态和家庭名称五个属性。online 布尔字段在这里有特殊的语义——它表示家庭成员是否在家,在线=在家,离线=外出。
const FAMILY_LIST: FamilyItem[] = [
new FamilyItem('石先生', '户主', '👨', true, '阳光花园'),
new FamilyItem('李女士', '成员', '👩', true, '阳光花园'),
new FamilyItem('小石', '成员', '👦', false, '阳光花园'),
new FamilyItem('石妈妈', '成员', '👵', true, '阳光花园'),
],
四口之家的设定非常贴近生活:夫妻二人加孩子和老人,孩子"小石"处于外出状态(可能在上学),其他三人都在家。这种真实感的数据设定,让整个应用的演示效果更加生动。
六个数据模型,六个业务领域,每个模型都有恰到好处的字段设计和初始数据。它们共同构成了这款智能家居应用的数据基石,也为后续的 UI 构建提供了清晰的数据契约。
七、组件主体结构
在了解了底层的数据模型和基础资源之后,我们终于可以进入页面的核心——组件主体了。在 ArkUI 中,一个页面就是一个被 @Entry 和 @Component 装饰的结构体。
@Entry
@Component
struct Page806 {
@State currentTab: number = 0;
@State currentRoom: number = 0;
@State addModal: boolean = false;
@State editModal: boolean = false;
@State delModal: boolean = false;
@State editIdx: number = -1;
@State delIdx: number = -1;
@State breath: boolean = false;
timer: number = -1;
@State deviceList: DeviceItem[] = DEVICE_LIST;
@State livingList: LivingItem[] = LIVING_LIST;
@State sceneList: SceneItem[] = SCENE_LIST;
@State energyList: EnergyItem[] = ENERGY_LIST;
@State securityList: SecurityItem[] = SECURITY_LIST;
@State familyList: FamilyItem[] = FAMILY_LIST;
这段代码定义了组件的所有状态变量。我们可以将它们分为四类。第一类是导航状态:currentTab(当前选中的底部 Tab 索引,默认为 0 即首页)和 currentRoom(当前选中的房间筛选索引,默认为 0 即全部)。第二类是弹窗状态:addModal、editModal、delModal 三个布尔值分别控制添加、编辑、删除三套弹窗的显示与隐藏,初始值均为 false(不显示)。第三类是索引与动画状态:editIdx 和 delIdx 记录当前操作的列表项索引,breath 是呼吸动画的布尔开关,timer 则是定时器的句柄。第四类是列表数据状态:六个数组分别对应六个数据模型的列表数据,它们都用 @State 装饰,确保数据变化时 UI 能够响应更新。
这里有一个值得注意的设计细节:所有的列表数据都先定义为外部的常量(如 DEVICE_LIST),然后在组件内部用 @State 装饰的变量重新赋值。这种模式的好处是,初始数据与组件状态分离,便于数据的复用和测试;同时,组件内部有自己的状态副本,可以自由修改而不会影响原始常量。
aboutToAppear() {
this.timer = setInterval(() => { this.breath = !this.breath; }, 1500);
}
aboutToDisappear() { clearInterval(this.timer); }
这两个是 ArkUI 的生命周期函数。aboutToAppear 在组件即将出现时调用,这里启动了一个 1500 毫秒的定时器,每隔 1.5 秒翻转一次 breath 布尔值,从而驱动呼吸动画效果。aboutToDisappear 在组件即将消失时调用,负责清除定时器,防止内存泄漏。
这种通过 setInterval + 布尔值翻转来驱动动画的方式,是 ArkUI 中实现循环动画的一种常用手法。相比于 animateTo 的一次性动画,定时器方案虽然精度稍低,但实现简单、控制方便,非常适合呼吸灯、图表涨落这类简单的循环动画效果。
build() {
Column() {
this.headerRoom()
Divider().strokeWidth(1).color(COLORS.line)
Scroll() {
Column() {
if (this.currentTab === 0) { this.tabHome() }
else if (this.currentTab === 1) { this.tabLiving() }
else if (this.currentTab === 2) { this.tabBedroom() }
else if (this.currentTab === 3) { this.tabKitchen() }
else if (this.currentTab === 4) { this.tabSecurity() }
else { this.tabMine() }
this.chartCard()
}
}
.layoutWeight(1)
.scrollBar(BarState.Off)
this.tabBar()
if (this.addModal) { this.panelAdd(() => { this.addModal = false }) }
if (this.editModal) { this.panelEdit(() => { this.editModal = false }) }
if (this.delModal) { this.panelDel(() => { this.delModal = false }) }
}
.backgroundColor(COLORS.bg)
.height('100%')
}
build() 方法是组件的核心,它以声明式的方式描述了整个页面的 UI 结构。最外层是一个 Column 纵向容器,占满整个屏幕高度,背景色为深黑色。内部从上到下依次排列四个部分:头部区域(headerRoom)、一条分割线(Divider)、可滚动的内容区域(Scroll)和底部 Tab 栏(tabBar)。
内容区域的设计很巧妙。Scroll 容器内部是一个 Column,其中包含两部分内容——上半部分是根据 currentTab 条件渲染的对应 Tab 的内容(通过 if-else 链实现 Tab 切换),下半部分是固定显示的能耗图表卡片(chartCard)。也就是说,无论切换到哪个 Tab,底部的能耗趋势图表都是可见的,它是一个跨 Tab 的公共信息模块。
Scroll 的 .layoutWeight(1) 属性非常关键——它让滚动区域占据剩余的全部空间,确保头部固定在顶部、底部 Tab 栏固定在底部,中间内容区域自适应高度并可滚动。.scrollBar(BarState.Off) 则隐藏了滚动条,让界面更加简洁。
页面的最上层是三个条件渲染的弹窗,当对应的布尔值为 true 时才会显示。弹窗采用绝对定位的覆盖层设计(后面会详细分析),点击遮罩或操作按钮后会调用传入的回调函数将布尔值设回 false,从而关闭弹窗。
整个组件主体的结构非常清晰:顶部固定头部 → 中间可滚动内容(Tab 内容 + 公共图表) → 底部固定导航 → 顶层弹窗覆盖。这是移动端应用最经典的页面布局范式之一。
八、头部区域详解
头部区域是用户打开应用后第一眼看到的内容,它承载着空间定位、环境信息和快捷操作三大功能。headerRoom 方法通过一个纵向 Column 组织了三行内容:小区信息与天气、房间切换标签、快捷操作按钮。
@Builder headerRoom() {
Column() {
Row() {
Column() {
Text('阳光花园').fontSize(20).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
Text('3室2厅 · 128㎡').fontSize(11).fontColor(COLORS.sub).margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start)
Blank()
Column() {
Row() {
Text('☀️').fontSize(22)
Text('28°').fontSize(20).fontWeight(FontWeight.Bold).fontColor(COLORS.warm).margin({ left: 4 })
}
Text('晴 · 空气质量优').fontSize(10).fontColor(COLORS.sub).margin({ top: 2 })
}
.alignItems(HorizontalAlign.End)
}
.width('100%').padding({ left: 16, right: 16, top: 12, bottom: 10 })
第一行是一个横向 Row,左右两侧各有一个信息块,中间用 Blank() 撑开。左侧显示家庭住址信息——小区名称"阳光花园"用 20号加粗的主标题色,户型和面积用 11号的次要文字色,形成清晰的层级。右侧显示天气信息——太阳图标 + 温度用大号暖白色高亮,天气描述用小号灰色文字。暖白色的温度数字在深色背景上非常醒目,让用户一眼就能看到当前的室外温度。
Scroll() {
Row() {
ForEach(ROOM_TABS, (room: string, idx: number) => {
Text(room)
.fontSize(13)
.fontColor(this.currentRoom === idx ? COLORS.green : COLORS.sub)
.backgroundColor(this.currentRoom === idx ? COLORS.chip : Color.Transparent)
.borderRadius(14)
.padding({ left: 14, right: 14, top: 6, bottom: 6 })
.margin({ right: 6 })
.onClick(() => { this.currentRoom = idx })
}, (room: string) => room)
}
.padding({ left: 12, right: 12 })
}
.scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off).margin({ bottom: 8 })
第二行是房间切换的横向滚动标签。外层是一个 Scroll 容器,设置为横向滚动并隐藏滚动条。内部是一个 Row,通过 ForEach 遍历 ROOM_TABS 数组生成标签。
每个标签的样式逻辑是典型的"选中/未选中"双态设计:选中状态下文字为绿色、背景为绿色胶囊底;未选中状态下文字为灰色、背景透明。点击标签时更新 currentRoom 的值,触发 UI 刷新,选中态随之切换。圆角 14px、左右内边距 14px 的胶囊形状,是移动端标签控件的经典样式。
Row() {
Row() { Text('🚪').fontSize(18); Text('离家').fontSize(13).fontColor(COLORS.title).margin({ left: 6 }) }
.layoutWeight(1).height(40).backgroundColor(COLORS.card).borderRadius(12)
.justifyContent(FlexAlign.Center).margin({ right: 6 })
.onClick(() => { this.addModal = true })
Row() { Text('🏠').fontSize(18); Text('回家').fontSize(13).fontColor(COLORS.title).margin({ left: 6 }) }
.layoutWeight(1).height(40).backgroundColor(COLORS.card).borderRadius(12)
.justifyContent(FlexAlign.Center).margin({ left: 6, right: 6 })
.onClick(() => { this.editModal = true })
Column() {
Text('今日能耗').fontSize(10).fontColor(COLORS.sub)
Row() {
Text('8.6').fontSize(16).fontWeight(FontWeight.Bold).fontColor(this.breath ? COLORS.green : COLORS.warm)
Text('kWh').fontSize(10).fontColor(COLORS.text3).margin({ left: 2 })
}.margin({ top: 2 })
}
.layoutWeight(1).height(40).backgroundColor(COLORS.chip).borderRadius(12)
.justifyContent(FlexAlign.Center).margin({ left: 6 })
}
.width('100%').padding({ left: 12, right: 12, bottom: 10 })
}
}
第三行是三个并排的快捷按钮,每个占 1/3 宽度(通过 layoutWeight(1) 实现等分)。左边两个是"离家"和"回家"的场景快捷按钮,深卡片背景,图标+文字居中排列。点击分别触发添加弹窗和编辑弹窗(这里是演示性质的触发)。
第三个按钮是"今日能耗"卡片,这是整个头部最有特色的设计。它采用绿色胶囊背景(COLORS.chip),与前两个卡片形成视觉区分。内部采用上下结构——上面是"今日能耗"标签文字,下面是大号数值 + 小号单位。
最精彩的是数值颜色的呼吸效果:fontColor(this.breath ? COLORS.green : COLORS.warm)。结合前面提到的 1.5 秒定时器,这个能耗数值会在深绿和暖白之间缓缓变色,仿佛在"呼吸"一般。这个微动效不仅增加了界面的活力,也暗示着能耗数据是"活"的、实时更新的。
整个头部区域虽然只有三行,但信息量很大——家庭位置、室外天气、房间筛选、快捷场景、实时能耗,全部浓缩在不到 200dp 的高度内。信息层次分明、交互便捷,是一个设计得相当紧凑而高效的头部区域。
九、Tab 0:首页——设备宫格
首页是用户进入应用后看到的第一个 Tab,采用设备宫格的布局形式,直观展示家中的核心设备状态。tabHome 方法构建了这个页面,内容分为三个区域:设备宫格区、添加设备按钮、快捷场景区。
9.1 设备卡片组件
@Builder deviceCard(i: number) {
let item = this.deviceList[i];
Column() {
Text(item.icon).fontSize(36).opacity(item.status === '开' ? 1 : 0.5)
Text(item.name).fontSize(14).fontWeight(FontWeight.Bold).fontColor(COLORS.title).margin({ top: 6 })
Row() {
Circle({ width: 8, height: 8 }).fill(item.status === '开' ? COLORS.green : COLORS.text3)
Text(item.status).fontSize(11).fontColor(item.status === '开' ? COLORS.green : COLORS.text3).margin({ left: 4 })
}.margin({ top: 4 })
Text(item.value).fontSize(20).fontWeight(FontWeight.Bold)
.fontColor(item.status === '开' ? COLORS.warm : COLORS.text3).margin({ top: 6 })
Text(item.room).fontSize(10).fontColor(COLORS.text3).margin({ top: 2 })
}
.layoutWeight(1).padding(14).backgroundColor(COLORS.card).borderRadius(16)
.onClick(() => { this.editIdx = i; this.editModal = true })
}
deviceCard 是一个可复用的设备卡片构建函数,接收一个索引参数 i,从 deviceList 中取出对应的数据进行渲染。卡片内部采用纵向排列,从上到下依次是:设备图标、设备名称、状态指示、设备数值、所属房间。
设备图标的处理很有细节——开启状态下不透明度为 1(完全显示),关闭状态下不透明度为 0.5(半透明)。这种视觉上的"明暗"差异,比单纯的文字状态更加直观。状态指示行采用了"小圆点 + 文字"的经典组合,8dp 的绿色/灰色圆点是状态的视觉锚点。
设备数值是卡片的视觉重心——20号加粗的暖白色大号字体,在深色卡片背景上非常突出。如果设备是关闭状态,数值则变为灰色的"—",与图标的半透明效果形成呼应。最底部的房间信息用最小号的灰色文字,属于最次要的辅助信息。
整个卡片用 16dp 的大圆角和 14dp 的内边距营造出"软糯"的质感,点击卡片会打开编辑弹窗进行设备控制。
9.2 宫格布局与快捷场景
@Builder tabHome() {
Column() {
Row() {
Text('我的设备').fontSize(18).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
Blank()
Text('全部 >').fontSize(12).fontColor(COLORS.green)
}
.width('92%').margin({ top: 12, bottom: 10 })
页面顶部是"我的设备"标题栏,左侧是加粗的标题文字,右侧是绿色的"全部 >"入口,暗示可以点击查看更多设备。标题栏宽度为 92%,留出两侧各 4% 的边距。
Row() {
ForEach(DEVICE_ROW0, (i: number) => {
this.deviceCard(i)
}, (i: number) => i.toString())
}
.width('92%').margin({ bottom: 10 })
Row() {
ForEach(DEVICE_ROW1, (i: number) => {
this.deviceCard(i)
}, (i: number) => i.toString())
}
.width('92%').margin({ bottom: 10 })
Row() {
ForEach(DEVICE_ROW2, (i: number) => {
this.deviceCard(i)
}, (i: number) => i.toString())
}
.width('92%').margin({ bottom: 12 })
设备宫格由三行 Row 组成,每行两个卡片。通过 DEVICE_ROW0、DEVICE_ROW1、DEVICE_ROW2 三个索引数组分别控制每行显示的设备。每行的两个卡片通过 layoutWeight(1) 实现等宽分布,卡片之间由 Row 容器自动分配间距。三行卡片的底部外边距逐行相同,形成整齐的网格效果。
Button('+ 添加设备')
.fontSize(13).fontColor(COLORS.title).backgroundColor(COLORS.chip)
.borderRadius(20).height(36)
.onClick(() => { this.addModal = true })
设备宫格下方是一个"+ 添加设备"按钮,绿色胶囊背景,20dp 的大圆角,36dp 的高度。点击按钮会弹出添加设备的弹窗。
Text('快捷场景').fontSize(16).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
.width('92%').margin({ top: 4, bottom: 8 })
Scroll() {
Row() {
ForEach([{ icon: '🌙', name: '睡眠', color: COLORS.blue }, { icon: '📖', name: '阅读', color: COLORS.warm }, { icon: '🎬', name: '观影', color: COLORS.orange }, { icon: '🚪', name: '离家', color: COLORS.green }, { icon: '🏠', name: '回家', color: COLORS.greenD }], (item: Object) => {
Column() {
Text(item.icon).fontSize(24)
Text(item.name).fontSize(11).fontColor(COLORS.sub).margin({ top: 4 })
}
.width(64).padding({ top: 12, bottom: 12 }).backgroundColor(COLORS.card)
.borderRadius(12).margin({ right: 8 }).alignItems(HorizontalAlign.Center)
.onClick(() => { this.editModal = true })
}, (item: Object) => item.name)
}
.padding({ left: 16, right: 16 })
}
.scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off).margin({ bottom: 12 })
}
}
页面底部是"快捷场景"区域,采用横向滚动的布局。五个场景入口以小卡片的形式排列——每个卡片 64dp 宽,内有图标和场景名称。这里的场景数据是直接内联在 ForEach 中的对象数组,每个场景有自己的主题色(虽然当前代码中 color 属性未直接使用,但保留了扩展能力)。
这种"宫格设备 + 横向快捷场景"的首页布局,是智能家居 App 的经典设计范式——上方展示用户最常用的设备状态,下方提供一键触发的场景入口,兼顾了信息查看和快捷操作两大需求。
十、Tab 1:客厅——设备列表开关
客厅 Tab 采用列表式布局,每个设备一行,右侧带 Toggle 开关,可以直接在列表中切换设备的开关状态。这种布局适合设备数量较多、需要频繁开关操作的场景。
@Builder tabLiving() {
Column() {
Row() {
Text('客厅设备').fontSize(18).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
Blank()
Text('6 个设备').fontSize(12).fontColor(COLORS.sub)
}
.width('92%').margin({ top: 12, bottom: 10 })
标题栏与首页类似,左侧是"客厅设备"标题,右侧显示设备总数"6 个设备",让用户对当前房间的设备数量一目了然。
ForEach(this.livingList, (item: LivingItem, idx: number) => {
Row() {
Column() { Text(item.icon).fontSize(28).opacity(item.isOn ? 1 : 0.5) }
.width(52).height(52).backgroundColor(item.isOn ? COLORS.chip : COLORS.card)
.borderRadius(12).justifyContent(FlexAlign.Center)
.border({ width: 1, color: item.isOn ? COLORS.greenD : COLORS.line })
设备列表通过 ForEach 遍历 livingList 生成。每个列表项是一个横向 Row,从左到右依次是:设备图标容器、设备信息、开关按钮。
图标容器是 52x52dp 的圆角方块,这里的双态设计比首页卡片更加丰富——开启时不仅图标不透明度为 1,背景色也变为绿色胶囊色(COLORS.chip),边框也变成深绿色;关闭时则是深色卡片背景和灰色边框。这种"背景色+边框+图标透明度"三重变化,让开关状态的视觉差异非常明显。
Column() {
Text(item.name).fontSize(15).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
Text(item.status).fontSize(12).fontColor(item.isOn ? COLORS.green : COLORS.text3).margin({ top: 4 })
}
.layoutWeight(1).margin({ left: 12 }).alignItems(HorizontalAlign.Start)
中间的设备信息区域占据剩余空间(layoutWeight(1)),设备名称用 15号加粗白色字体,状态描述用 12号字体。状态文字的颜色也随开关状态变化——开启时绿色,关闭时灰色,与左侧图标容器的视觉状态保持一致。
Toggle({ type: ToggleType.Switch, isOn: item.isOn })
.selectedColor(COLORS.green).switchPointColor(COLORS.title)
.onChange((isOn: boolean) => { this.livingList[idx].isOn = isOn; })
}
.width('92%').padding(14).backgroundColor(COLORS.card).borderRadius(14)
.margin({ bottom: 8 })
.onClick(() => { this.editIdx = idx; this.editModal = true })
}, (item: LivingItem) => item.name)
最右侧是 Toggle 开关组件,这是 ArkUI 内置的开关控件。type: ToggleType.Switch 指定为开关样式,isOn: item.isOn 将开关状态与数据双向绑定。.selectedColor(COLORS.green) 设置开关开启时的背景色为主题绿色,.switchPointColor(COLORS.title) 设置开关滑块的颜色为暖白色。
.onChange 回调函数在用户切换开关时触发,将 livingList 中对应索引项的 isOn 属性更新为新值。由于 LivingItem 类被 @Observed 装饰,且 livingList 被 @State 装饰,这个属性变更会自动触发 UI 刷新——图标容器的背景色、状态文字的颜色都会随之变化。
每个列表项都有一个点击事件,点击后打开编辑弹窗进行更详细的设备控制。注意这里有一个交互细节:Toggle 开关的点击和整行的点击是分开的——点击 Toggle 区域切换开关,点击其他区域打开详情,这需要框架在事件分发时正确处理触摸区域。
Row() {
Button('全部开启')
.fontSize(13).fontColor(COLORS.title).backgroundColor(COLORS.chip)
.borderRadius(18).height(34).layoutWeight(1)
Button('全部关闭')
.fontSize(13).fontColor(COLORS.title).backgroundColor(COLORS.chip)
.borderRadius(18).height(34).layoutWeight(1).margin({ left: 10 })
.onClick(() => { this.delModal = true })
}
.width('92%').margin({ top: 4, bottom: 12 })
}
}
列表底部是两个并排的批量操作按钮:“全部开启"和"全部关闭”。两个按钮平分宽度,样式相同。当前代码中"全部关闭"按钮被绑定了删除弹窗的触发事件(演示用途),而"全部开启"按钮暂未绑定事件。在实际项目中,这两个按钮应该分别实现批量开启和关闭所有客厅设备的功能。
客厅 Tab 的设计重点在于操作效率——列表布局信息密度高,Toggle 开关一键切换,批量按钮快速全关全开,非常适合客厅这种设备多、操作频繁的空间。
十一、Tab 2:卧室——场景模式卡
卧室 Tab 的核心是场景模式,采用了横向滚动的大卡片布局,每个场景卡片有独立的激活态样式。页面还包含环境数据展示和新建场景入口。
@Builder tabBedroom() {
Column() {
Row() {
Text('场景模式').fontSize(18).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
Blank()
Text('自定义 >').fontSize(12).fontColor(COLORS.green)
}
.width('92%').margin({ top: 12, bottom: 10 })
标题栏右侧是"自定义 >"入口,暗示用户可以创建和编辑自己的场景模式。
Scroll() {
Row() {
ForEach(this.sceneList, (item: SceneItem, idx: number) => {
Column() {
Column() { Text(item.icon).fontSize(42) }
.width(80).height(80).backgroundColor(item.active ? COLORS.chip : COLORS.bg)
.borderRadius(40).justifyContent(FlexAlign.Center)
.border({ width: 2, color: item.active ? COLORS.green : COLORS.line })
场景卡片区域是横向滚动的,每个卡片 130dp 宽。卡片顶部是一个 80dp 的圆形图标容器——激活态下是绿色胶囊背景 + 绿色边框,未激活态下是深色背景 + 灰色边框。42号的大图标是卡片的视觉焦点,圆形的设计给人一种柔和、亲近的感觉,非常适合卧室场景。
Text(item.name).fontSize(14).fontWeight(FontWeight.Bold)
.fontColor(item.active ? COLORS.green : COLORS.title).margin({ top: 12 })
Text(item.desc).fontSize(11).fontColor(COLORS.sub).maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis }).margin({ top: 4 })
.textAlign(TextAlign.Center)
if (item.active) {
Text('已激活').fontSize(10).fontColor(COLORS.green).backgroundColor(COLORS.chip)
.padding({ left: 8, right: 8, top: 3, bottom: 3 }).borderRadius(10).margin({ top: 8 })
}
}
.width(130).padding(16).backgroundColor(COLORS.card).borderRadius(16)
.margin({ right: 10 }).alignItems(HorizontalAlign.Center)
图标下方是场景名称,激活态时变为绿色,未激活时为白色。再下面是场景描述文字,最多显示两行,超出部分用省略号截断,居中对齐。如果场景处于激活状态,还会显示一个"已激活"的标签——绿色文字配绿色胶囊背景,进一步强化激活态的视觉信号。
.onClick(() => {
for (let j = 0; j < this.sceneList.length; j++) { this.sceneList[j].active = (j === idx); }
})
}, (item: SceneItem) => item.name)
}
.padding({ left: 16, right: 16, top: 4, bottom: 4 })
}
.scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off).margin({ bottom: 12 })
点击场景卡片时的逻辑很有意思:遍历整个 sceneList 数组,将当前点击的索引对应的场景设为激活(active = true),其他所有场景设为非激活(active = false)。这实现了场景的"单选"效果——同一时间只能有一个场景处于激活状态。这符合场景模式的业务逻辑:你不可能同时处于"睡眠模式"和"观影模式"。
Text('环境数据').fontSize(16).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
.width('92%').margin({ bottom: 8 })
Row() {
ForEach([{ icon: '🌡️', label: '温度', val: '26°C' }, { icon: '💧', label: '湿度', val: '55%' }, { icon: '🔊', label: '噪音', val: '32dB' }], (item: Object, idx: number) => {
Column() {
Text(item.icon).fontSize(24)
Text(item.label).fontSize(11).fontColor(COLORS.sub).margin({ top: 4 })
Text(item.val).fontSize(14).fontWeight(FontWeight.Bold).fontColor(COLORS.title).margin({ top: 2 })
}
.layoutWeight(1).padding({ top: 12, bottom: 12 }).backgroundColor(COLORS.card)
.borderRadius(12).alignItems(HorizontalAlign.Center)
.margin(idx === 1 ? { left: 8, right: 8 } : {})
}, (item: Object, idx: number) => idx.toString())
}
.width('92%').margin({ bottom: 12 })
场景卡片下方是"环境数据"区域,三个并排的小卡片分别展示温度(26°C)、湿度(55%)和噪音(32dB)。每个小卡片采用纵向布局——图标在上,标签在中,数值在下。数值用加粗的白色字体突出显示。中间的卡片左右各有 8dp 的间距,形成三等分的均匀布局。
这组环境数据与场景模式形成了很好的互补——场景是"主动控制",环境数据是"被动感知",用户既可以一键切换场景,也可以实时了解卧室内的环境状况。
Button('+ 新建场景')
.fontSize(13).fontColor(COLORS.title).backgroundColor(COLORS.chip)
.borderRadius(20).height(36)
.onClick(() => { this.addModal = true })
}
}
页面底部是"+ 新建场景"按钮,样式与首页的添加设备按钮一致。点击后弹出添加弹窗(演示用途),在实际项目中会打开场景创建界面。
卧室 Tab 的设计重点在于场景的可视化表达——大圆形图标、激活态高亮、横向滑动浏览,让场景切换变得直观而富有仪式感。对于卧室这个强调氛围和体验的空间来说,这种设计非常契合。
十二、Tab 3:厨房——能耗统计卡
厨房 Tab 聚焦于家庭能耗数据的展示,采用数据卡片 + 排名列表的形式,让用户对家庭的能源消耗情况一目了然。
12.1 能耗数据卡片
@Builder energyCard(i: number) {
let item = this.energyList[i];
Column() {
Row() {
Text(item.icon).fontSize(20)
Text(item.label).fontSize(12).fontColor(COLORS.sub).margin({ left: 6 })
}.width('100%')
Row() {
Text(item.value).fontSize(28).fontWeight(FontWeight.Bold).fontColor(COLORS.warm)
Text(item.unit).fontSize(12).fontColor(COLORS.text3).margin({ left: 4, top: 8 })
}.width('100%').margin({ top: 8 })
Text(item.trend).fontSize(11)
.fontColor(item.trend.startsWith('↓') ? COLORS.green : COLORS.orange)
.width('100%').margin({ top: 4 })
}
.layoutWeight(1).padding(14).backgroundColor(COLORS.card).borderRadius(14)
}
energyCard 是能耗卡片的构建函数,每张卡片包含三行信息。第一行是图标 + 标签(如"今日用电"),图标和文字左对齐排列。第二行是核心数值 + 单位,数值用 28号加粗暖白色大字,非常醒目;单位用 12号灰色小字,位置略偏下(margin({ top: 8 })),形成错落的视觉节奏。第三行是同比趋势,下降箭头显示绿色(节能好事),上升箭头显示橙色(需关注)。
这种数据卡片的设计遵循了"金字塔"原则——最重要的数值最大最醒目,次要信息依次缩小灰度,用户的视线会自然地被大数字吸引,然后再读取周围的辅助信息。
12.2 能耗排名与详情
@Builder tabKitchen() {
Column() {
Row() {
Text('能耗统计').fontSize(18).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
Blank()
Text('今日').fontSize(12).fontColor(COLORS.green).backgroundColor(COLORS.chip)
.padding({ left: 10, right: 10, top: 4, bottom: 4 }).borderRadius(12)
}
.width('92%').margin({ top: 12, bottom: 10 })
标题栏右侧有一个"今日"的时间筛选标签,绿色胶囊背景,表示当前查看的是今日数据。在实际应用中,这里可能是一个可以切换"今日/本周/本月"的选择器。
Row() {
ForEach(ENERGY_ROW0, (i: number) => { this.energyCard(i) }, (i: number) => i.toString())
}
.width('92%').margin({ bottom: 10 })
Row() {
ForEach(ENERGY_ROW1, (i: number) => { this.energyCard(i) }, (i: number) => i.toString())
}
.width('92%').margin({ bottom: 12 })
四张能耗卡片分成两行排列,每行两张。第一行是今日用电和今日用气,第二行是今日用水和本月总计。通过 ENERGY_ROW0 和 ENERGY_ROW1 两个索引数组控制每行的内容,与首页设备宫格的布局手法一致。
Column() {
Row() {
Text('📊 设备能耗排名').fontSize(14).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
Blank()
Text('今日').fontSize(11).fontColor(COLORS.sub)
}
.width('100%').margin({ bottom: 10 })
再往下是"设备能耗排名"卡片,标题栏右侧标注"今日"时间范围。
ForEach(ENERGY_RANK, (item: string, idx: number) => {
Row() {
Text((idx + 1).toString()).fontSize(12)
.fontColor(idx < 3 ? COLORS.warm : COLORS.text3).width(18)
Text(item.split(' ')[0]).fontSize(13).fontColor(COLORS.title).layoutWeight(1)
Text(item.split(' ')[1]).fontSize(12).fontColor(COLORS.sub).margin({ right: 8 })
Column()
.width((idx === 0 ? 100 : idx === 1 ? 60 : idx === 2 ? 40 : 28))
.height(6).backgroundColor(idx < 3 ? COLORS.green : COLORS.line).borderRadius(3)
}
.width('100%').margin({ bottom: 8 })
}, (item: string) => item)
}
.width('92%').padding(14).backgroundColor(COLORS.card).borderRadius(14).margin({ bottom: 12 })
排名列表的每一行包含四个部分:排名数字、设备名称、百分比数值、进度条。
排名数字的设计很有细节——前三名使用暖白色高亮,第四名及以后使用灰色。这是一种常见的"领奖台"设计思路,突出头部排名。设备名称占据中间的弹性空间,右对齐显示百分比数值。
进度条是排名列表的视觉亮点。每个进度条的宽度根据排名动态计算:第一名 100dp(最宽),第二名 60dp,第三名 40dp,第四名及以后 28dp。前三名的进度条用主题绿色填充,后面的用灰色线条。这种条形图与排名结合的方式,让数据大小关系一目了然——用户甚至不需要看具体的百分比数字,只通过进度条的长短就能判断各设备的能耗占比。
这里使用 item.split(' ') 来从字符串中拆分设备名称和百分比,是一种简单的数据解析方式。在实际项目中,可能会使用更结构化的数据模型,但对于演示场景,这种字符串拆分的方式足够轻便。
Button('查看详细账单')
.fontSize(13).fontColor(COLORS.title).backgroundColor(COLORS.chip)
.borderRadius(20).height(36)
.onClick(() => { this.editModal = true })
}
}
页面底部是"查看详细账单"按钮,点击后可进入更详细的账单分析页面。
厨房 Tab 的设计重点在于数据的可视化呈现——通过数字卡片、进度条排名、趋势指标等多种形式,将抽象的能耗数据转化为直观的视觉信息。能耗管理是智能家居的重要价值点之一,好的数据展示能够帮助用户发现节能空间、降低生活成本。
十三、Tab 4:安防——监控卡片
安防 Tab 专注于家庭安全设备的状态监控和告警管理,采用列表式的监控卡片布局,配合告警记录区域,为用户构建了一道可视化的安全防线。
@Builder tabSecurity() {
Column() {
Row() {
Text('安防监控').fontSize(18).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
Blank()
Row() {
Circle({ width: 8, height: 8 }).fill(COLORS.green)
Text('布防中').fontSize(12).fontColor(COLORS.green).margin({ left: 4 })
}
.backgroundColor(COLORS.chip)
.padding({ left: 10, right: 10, top: 4, bottom: 4 }).borderRadius(12)
}
.width('92%').margin({ top: 12, bottom: 10 })
标题栏右侧是安防系统的全局状态标签——“布防中”,绿色小圆点 + 绿色文字 + 绿色胶囊背景,三重绿色信号传递出"一切安全"的信息。这个全局状态标签让用户在进入安防页面的第一时间就能了解整个安防系统的运行状态。
ForEach(this.securityList, (item: SecurityItem, idx: number) => {
Row() {
Column() { Text(item.icon).fontSize(36) }
.width(72).height(72).backgroundColor(COLORS.bg).borderRadius(10)
.justifyContent(FlexAlign.Center).border({ width: 1, color: COLORS.line })
安防设备列表通过 ForEach 遍历 securityList 生成。每个设备卡片是一行横向布局,左侧是 72x72dp 的设备预览框——深色背景 + 灰色边框 + 圆角,模拟摄像头画面的效果。36号的大图标居中显示,代表不同类型的安防设备。
Column() {
Row() {
Text(item.location).fontSize(14).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
.layoutWeight(1).maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis })
Row() {
Circle({ width: 6, height: 6 }).fill(statusColor(item.status))
Text(item.status).fontSize(11).fontColor(statusColor(item.status)).margin({ left: 4 })
}
}
.width('100%')
右侧信息区域的第一行是设备位置 + 状态。位置名称左对齐,用 14号加粗白色字体,最多显示一行,超出则省略号截断。状态信息右对齐,使用了前面介绍的 statusColor 函数——根据"在线/离线/告警"三种状态分别返回绿色、灰色和橙色。小圆点 + 文字的组合与其他页面的状态展示保持一致,形成了统一的设计语言。
Text('⏱️ 上次活动:' + item.lastActive).fontSize(11).fontColor(COLORS.sub).margin({ top: 8 })
第二行是上次活动时间,用时钟图标 + 文字的形式展示。这个信息很重要——它告诉用户这个设备"多久没有动静了",对于判断设备是否正常工作很有参考价值。比如"1小时前"的摄像头可能正常,但"一天前"的烟雾报警器就需要检查一下了。
Row() {
Text('查看').fontSize(11).fontColor(COLORS.green).backgroundColor(COLORS.chip)
.padding({ left: 10, right: 10, top: 4, bottom: 4 }).borderRadius(10).margin({ right: 6 })
if (item.status === '告警') {
Text('处理').fontSize(11).fontColor(COLORS.title).backgroundColor(COLORS.orange)
.padding({ left: 10, right: 10, top: 4, bottom: 4 }).borderRadius(10)
}
}
.width('100%').margin({ top: 8 })
}
.layoutWeight(1).margin({ left: 12 }).alignItems(HorizontalAlign.Start)
第三行是操作按钮。每个设备都有一个"查看"按钮,绿色文字配绿色胶囊底。如果设备处于"告警"状态,还会额外显示一个橙色的"处理"按钮。这种条件渲染的设计非常合理——告警状态需要用户立即处理,所以增加了醒目的操作入口;而正常状态下只需要"查看"即可。
橙色的"处理"按钮在整体绿色的安防页面中非常突出,能够有效吸引用户的注意力,确保告警信息不会被忽略。
}
.width('92%').padding(12).backgroundColor(COLORS.card).borderRadius(14)
.margin({ bottom: 8 })
.onClick(() => { this.editIdx = idx; this.editModal = true })
}, (item: SecurityItem) => item.location)
整行卡片可点击,点击后打开编辑弹窗(演示用途)。在实际应用中,点击摄像头卡片可能会跳转到实时监控画面。
Button('+ 添加设备')
.fontSize(13).fontColor(COLORS.title).backgroundColor(COLORS.chip)
.borderRadius(20).height(36)
.onClick(() => { this.addModal = true })
列表下方是"+ 添加设备"按钮,样式与其他页面保持一致。
// 告警记录
Text('告警记录').fontSize(16).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
.width('92%').margin({ top: 4, bottom: 8 })
Column() {
ForEach([{ time: '10分钟前', type: '窗户传感器', level: '警告', desc: '卧室窗户异常开启' }, { time: '2小时前', type: '移动侦测', level: '提醒', desc: '客厅检测到移动' }, { time: '昨天', type: '门铃', level: '信息', desc: '有人按响前门门铃' }], (item: Object, idx: number) => {
Row() {
Column() {
Circle({ width: 10, height: 10 })
.fill(item.level === '警告' ? COLORS.orange : item.level === '提醒' ? COLORS.warm : COLORS.blue)
}
.width(24).justifyContent(FlexAlign.Center)
页面底部是告警记录区域,展示最近的三条告警信息。每条记录左侧是一个彩色圆点,表示告警等级:警告=橙色、提醒=暖白色、信息=蓝色。三级告警用三种不同的颜色区分,符合安防系统的通用设计规范。
Column() {
Row() {
Text(item.type).fontSize(13).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
Blank()
Text(item.level).fontSize(10)
.fontColor(item.level === '警告' ? COLORS.orange : item.level === '提醒' ? COLORS.warm : COLORS.blue)
.backgroundColor(item.level === '警告' ? 'rgba(249,115,22,0.15)' : item.level === '提醒' ? 'rgba(245,222,179,0.15)' : 'rgba(59,130,246,0.15)')
.padding({ left: 6, right: 6, top: 2, bottom: 2 }).borderRadius(8)
}
.width('100%')
Text(item.desc).fontSize(11).fontColor(COLORS.sub).margin({ top: 2 })
Text(item.time).fontSize(10).fontColor(COLORS.text3).margin({ top: 4 })
}
.layoutWeight(1).margin({ left: 8 }).alignItems(HorizontalAlign.Start)
}
.width('100%').padding({ left: 12, right: 12, top: 10, bottom: 10 })
告警内容区域包含三行信息:第一行是告警类型(左)+ 等级标签(右),第二行是告警描述,第三行是告警时间。等级标签采用了"彩色文字 + 半透明彩色背景"的设计——警告是橙字橙底、提醒是暖白字暖白底、信息是蓝字蓝底,透明度均为 15%。这种半透明背景的标签设计既保持了色彩识别度,又不会过于刺眼。
if (idx < 2) { Divider().strokeWidth(1).color(COLORS.line).margin({ left: 44 }) }
}, (item: Object, idx: number) => idx.toString())
}
.width('92%').backgroundColor(COLORS.card).borderRadius(14).margin({ bottom: 12 })
}
}
三条记录之间用分割线分隔,分割线左侧留出 44dp 的缩进,与圆点对齐,形成清晰的时间轴视觉效果。
安防 Tab 的设计重点在于安全感的传达——通过状态颜色、告警等级、时间记录等元素,构建了一个完整的安全监控视图。用户打开这个页面,就能全面掌握家中安防设备的运行状态和历史告警情况。
十四、Tab 5:我的——家庭管理卡
"我的"Tab 是个人中心页面,采用了典型的个人中心式布局——顶部用户信息卡、家庭成员列表、房屋信息、功能设置列表。这个页面虽然交互相对简单,但信息层次丰富,是整个应用的管理中枢。
@Builder tabMine() {
Column() {
Column() {
Column() { Text('👨').fontSize(48) }
.width(80).height(80).backgroundColor(COLORS.chip).borderRadius(40)
.justifyContent(FlexAlign.Center).border({ width: 2, color: COLORS.green })
Text('石先生').fontSize(18).fontWeight(FontWeight.Bold).fontColor(COLORS.title).margin({ top: 12 })
Text('户主 · 阳光花园').fontSize(12).fontColor(COLORS.sub).margin({ top: 4 })
页面顶部是用户信息卡片。80dp 的圆形头像居中,绿色胶囊背景 + 绿色边框,48号的男士 emoji 作为头像。头像下方是用户姓名和身份描述(户主 + 小区名)。这种居中的头像+姓名布局是个人中心页面的经典设计。
Row() {
ForEach([{ num: '4', label: '家庭成员' }, { num: '12', label: '智能设备' }, { num: '6', label: '场景模式' }], (item: Object, idx: number) => {
Column() {
Text(item.num).fontSize(18).fontWeight(FontWeight.Bold).fontColor(COLORS.warm)
Text(item.label).fontSize(11).fontColor(COLORS.sub).margin({ top: 2 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
if (idx < 2) { Column().width(1).height(36).backgroundColor(COLORS.line) }
}, (item: Object, idx: number) => idx.toString())
}
.width('100%').margin({ top: 16 })
}
.width('92%').padding(20).backgroundColor(COLORS.card).borderRadius(16)
.margin({ top: 12 }).alignItems(HorizontalAlign.Center)
用户信息下方是三栏统计数据:家庭成员数(4人)、智能设备数(12台)、场景模式数(6个)。每栏都是"数字+标签"的纵向布局,数字用暖白色加粗高亮。三栏之间用竖线分隔,竖线高度 36dp,形成整齐的统计面板。
这组统计数据是整个应用的"数据总览"——用户数、设备数、场景数,三个数字概括了家庭智能化的规模。
Row() {
Text('家庭成员').fontSize(16).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
Blank()
Text('管理 >').fontSize(12).fontColor(COLORS.green)
}
.width('92%').margin({ top: 16, bottom: 10 })
Column() {
ForEach(this.familyList, (item: FamilyItem) => {
Row() {
Column() { Text(item.avatar).fontSize(26) }
.width(44).height(44).backgroundColor(COLORS.chip).borderRadius(22)
.justifyContent(FlexAlign.Center)
Column() {
Text(item.name).fontSize(14).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
Text(item.role).fontSize(11).fontColor(COLORS.sub).margin({ top: 2 })
}
.margin({ left: 10 }).alignItems(HorizontalAlign.Start)
Blank()
Row() {
Circle({ width: 8, height: 8 }).fill(item.online ? COLORS.green : COLORS.text3)
Text(item.online ? '在家' : '外出')
.fontSize(11).fontColor(item.online ? COLORS.green : COLORS.sub).margin({ left: 4 })
}
}
.width('100%').padding({ left: 12, right: 12, top: 10, bottom: 10 })
}, (item: FamilyItem) => item.name)
}
.width('92%').backgroundColor(COLORS.card).borderRadius(14)
家庭成员列表的每一行包含头像、姓名+角色、在家状态三部分。44dp 的圆形头像使用绿色胶囊背景。中间是成员姓名和角色标签。右侧是在家/外出状态——绿色小圆点 + "在家"文字表示在家,灰色小圆点 + "外出"文字表示外出。
这个"在家/外出"状态是智能家居中很实用的功能。它可以结合手机定位、WiFi 连接等方式判断家庭成员是否在家,从而实现更智能的自动化场景(比如所有人离家后自动布防)。
Row() {
Text('房屋信息').fontSize(16).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
Blank()
Text('编辑 >').fontSize(12).fontColor(COLORS.green)
}
.width('92%').margin({ top: 16, bottom: 10 })
Column() {
Row() {
Text('🏠').fontSize(20)
Column() {
Text('阳光花园').fontSize(14).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
Text('3室2厅 · 128㎡').fontSize(11).fontColor(COLORS.sub).margin({ top: 2 })
}
.margin({ left: 10 }).alignItems(HorizontalAlign.Start)
Blank()
Text('管理 >').fontSize(12).fontColor(COLORS.green)
}
.width('100%').padding(12)
}
.width('92%').backgroundColor(COLORS.card).borderRadius(14).margin({ bottom: 8 })
房屋信息卡片展示了当前管理的房屋信息——小区名和户型面积。右侧有"管理 >"入口,可以进行多房屋管理(如果用户有多处房产的话)。
Column() {
ForEach(['⚙️ 系统设置', '🔔 通知管理', '📶 设备管理', '❓ 帮助与反馈'], (item: string, idx: number) => {
Row() {
Text(item).fontSize(14).fontColor(COLORS.title)
Blank()
Text('>').fontSize(14).fontColor(COLORS.text3)
}
.width('100%').padding({ left: 14, right: 14, top: 14, bottom: 14 })
.onClick(() => { if (idx === 0) { this.editModal = true } else if (idx === 2) { this.delModal = true } })
}, (item: string) => item)
}
.width('92%').backgroundColor(COLORS.card).borderRadius(14).margin({ bottom: 16 })
}
}
页面最底部是功能设置列表,包含四项:系统设置、通知管理、设备管理、帮助与反馈。每个菜单项都是左图标文字 + 右箭头的标准列表样式。点击不同的项会触发不同的弹窗(演示用途)。
"我的"Tab 的设计重点在于信息聚合与管理入口——把用户信息、家庭成员、房屋信息、系统设置等分散的管理功能集中在一个页面,用户可以在这里找到所有需要配置和管理的内容。虽然交互相对简单,但信息结构清晰、层级分明,是一个合格的个人中心页面。
十五、图表卡片
能耗趋势图表是一个跨 Tab 的公共模块,无论切换到哪个页面,它都会显示在内容区域的底部。这张图表用纯代码的方式绘制了一组柱状图,展示了近六个月的家庭能耗趋势,是整个应用数据可视化的亮点之一。
@Builder chartCard() {
Column() {
Row() {
Text('📈 家庭能耗趋势').fontSize(15).fontWeight(FontWeight.Bold).fontColor(COLORS.title)
Blank()
Text('近6个月').fontSize(12).fontColor(COLORS.text3)
}
.width('100%').margin({ bottom: 12 })
图表卡片的标题栏左侧是"📈 家庭能耗趋势"标题,右侧是"近6个月"的时间范围说明,用灰色小字低调呈现。
Row() {
ForEach(MONTH_IDX, (i: number) => {
Column() {
Column()
.width(22)
.height(this.breath ? ENERGY_VAL[i] * 0.22 : ENERGY_VAL[i] * 0.18)
.backgroundColor(i === 5 ? COLORS.green : COLORS.greenD)
.borderRadius({ topLeft: 6, topRight: 6 }).margin({ top: 8 })
Text(ENERGY_VAL[i].toString()).fontSize(10).fontColor(COLORS.sub).margin({ top: 4 })
Text(MONTH_NAME[i]).fontSize(10).fontColor(COLORS.text3).margin({ top: 2 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
}, (i: number) => i.toString())
}
.width('100%').height(130).alignItems(VerticalAlign.Bottom)
柱状图的核心实现非常巧妙。外层是一个 130dp 高的 Row 容器,设置了 alignItems(VerticalAlign.Bottom),让内部元素从底部开始排列——这是柱状图"从下往上长"的关键。
内部通过 ForEach 遍历 MONTH_IDX(0到5),为每个月生成一个柱形。每个柱形包含三部分:上方的柱形条、中间的数值标签、下方的月份标签。
柱形条是一个纯 Column 容器(没有子元素,仅作为色块使用),宽度固定为 22dp,高度由能耗数值动态计算:ENERGY_VAL[i] * 0.22 或 ENERGY_VAL[i] * 0.18。乘以 0.22 是为了将 300-500 范围的能耗值映射到 60-110dp 的高度范围内,确保柱状图在 130dp 的容器内有合适的比例。
颜色方面,最后一个月(当前月,索引为5)使用亮绿色(COLORS.green),前面的月份使用深绿色(COLORS.greenD)。这种"当前月高亮"的设计手法,能够引导用户关注最新的数据。
柱形的顶部使用了 borderRadius({ topLeft: 6, topRight: 6 }),只给上面两个角设置圆角,底部保持直角,形成了经典的柱状图样式。
最精彩的是呼吸动画效果:柱形的高度会在 ENERGY_VAL[i] * 0.22 和 ENERGY_VAL[i] * 0.18 之间切换,由 this.breath 布尔值控制。当 breath 为 true 时柱子更高(乘以 0.22),为 false 时柱子稍矮(乘以 0.18)。结合 1.5 秒的定时器,所有柱子会同步"呼吸"——缓缓升高又缓缓降低,仿佛数据在脉动。这个微动效为静态的图表增添了生命力,也暗示了数据的实时性。
Divider().strokeWidth(1).color(COLORS.line).margin({ top: 12, bottom: 12 })
Row() {
ForEach([{ label: '本月用电', val: '256 kWh', color: COLORS.warm }, { label: '预估费用', val: '¥153.6', color: COLORS.green }, { label: '环比变化', val: '↓ 12.5%', color: COLORS.green }], (item: Object, idx: number) => {
Column() {
Text(item.label).fontSize(11).fontColor(COLORS.sub)
Text(item.val).fontSize(16).fontWeight(FontWeight.Bold).fontColor(item.color).margin({ top: 2 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
if (idx < 2) { Column().width(1).height(32).backgroundColor(COLORS.line) }
}, (item: Object, idx: number) => idx.toString())
}
.width('100%')
}
.width('92%').padding(16).backgroundColor(COLORS.card).borderRadius(14)
.margin({ top: 4, bottom: 16 })
}
图表下方用一条分割线隔开,再往下是三栏统计数据:本月用电(256 kWh,暖白色)、预估费用(¥153.6,绿色)、环比变化(↓ 12.5%,绿色)。每栏都是"标签+数值"的纵向布局,数值的颜色根据内容不同而变化——用电量大用暖白色突出,费用和节能效果用绿色表示正面信息。三栏之间用竖线分隔,与"我的"页面的统计面板设计风格一致。
整个图表卡片虽然没有使用任何专业的图表库,完全靠基础组件搭建,但效果却相当不错。它展示了 ArkUI 的灵活性——只要创意足够,用最基础的 Column、Row、Text 组件也能组合出相当专业的数据可视化效果。
十六、底部 Tab 栏
底部 Tab 栏是整个应用的导航中枢,六个 Tab 之间的切换全靠它。虽然结构简单,但它的设计细节同样值得品味。
@Builder tabBar() {
Row() {
ForEach(TAB_LIST, (tab: TabMeta, idx: number) => {
Column() {
Text(tab.icon).fontSize(this.currentTab === idx ? 24 : 20)
Text(tab.label)
.fontSize(11)
.fontColor(this.currentTab === idx ? COLORS.tabOn : COLORS.text3)
.fontWeight(this.currentTab === idx ? FontWeight.Bold : FontWeight.Normal)
.margin({ top: 2 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Center)
.onClick(() => { this.currentTab = idx })
}, (tab: TabMeta) => tab.label)
}
.width('100%').height(58).backgroundColor(COLORS.card)
.padding({ top: 6, bottom: 8 }).border({ width: { top: 1 }, color: COLORS.line })
}
底部 Tab 栏的实现非常简洁。外层是一个 58dp 高的 Row 容器,深色卡片背景,顶部有一条 1dp 的分割线。内部通过 ForEach 遍历 TAB_LIST 数组,为每个 Tab 生成一个按钮。
每个 Tab 按钮是一个纵向 Column,通过 layoutWeight(1) 实现等分宽度。内部包含图标和文字两行:图标在上,文字在下。
选中态和未选中态的区别体现在三个维度:第一是图标大小——选中态 24号,未选中态 20号,选中的图标略大一些;第二是文字颜色——选中态是主题绿色(COLORS.tabOn),未选中态是辅助灰色(COLORS.text3);第三是字重——选中态是加粗(FontWeight.Bold),未选中态是常规(FontWeight.Normal)。
这三重变化(大小+颜色+字重)虽然每一个变化都很细微,但叠加在一起后,选中态的视觉突出效果非常明显。这是移动端 Tab 栏的经典设计手法——通过多维度的微妙差异,既保证了选中态的可识别性,又不会让界面显得过于跳跃。
点击 Tab 时,将 currentTab 更新为对应的索引值。由于 currentTab 被 @State 装饰,这个赋值会触发整个页面的重新渲染——内容区域切换到对应的 Tab 页面,底部 Tab 栏的选中态也同步更新。整个切换过程由 ArkUI 的响应式系统自动完成,开发者只需要关心数据状态,不需要手动操作 DOM/视图。
底部 Tab 栏的设计虽然简单,但它承担着整个应用的导航重任。六个 Tab 的图标和文字都使用了易于理解的 emoji 和中文标签,用户可以快速识别每个 Tab 的功能。58dp 的高度也符合移动端的交互规范,确保拇指点击的舒适性。
十七、弹窗系统
弹窗是移动端应用中不可或缺的交互组件,用于承载添加、编辑、确认等临时性操作。该源码实现了三套弹窗——添加设备弹窗、设备控制弹窗、确认移除弹窗,它们共享同一个遮罩层逻辑,形成了一套完整的弹窗系统。
17.1 通用遮罩层
@Builder modalOverlay(onClose: () => void) {
Stack() {
Column().width('100%').height('100%').backgroundColor(COLORS.mask)
}
.width('100%').height('100%').alignContent(Alignment.Center)
.onClick(() => onClose())
}
modalOverlay 是通用遮罩层构建函数,接收一个 onClose 回调函数作为参数。遮罩层的实现非常简单——一个占满全屏的 Stack 容器,内部是一个同样占满全屏的深色半透明 Column(COLORS.mask,即 rgba(0,0,0,0.65),65% 不透明度的黑色)。
点击遮罩层的任意位置都会调用 onClose 回调,从而关闭弹窗。这是弹窗交互的标准行为——点击背景取消。
遮罩层的作用有两个:一是视觉聚焦,通过半透明黑色背景弱化后面的内容,将用户的注意力引导到弹窗上;二是事件拦截,遮罩层覆盖了整个屏幕,阻止用户点击后面的内容,确保弹窗处于模态状态。
17.2 添加设备弹窗
@Builder panelAdd(onClose: () => void) {
Stack() {
this.modalOverlay(onClose)
Column() {
Text('➕ 添加设备').fontSize(18).fontWeight(FontWeight.Bold).fontColor(COLORS.title).margin({ bottom: 16 })
Column() {
Text('设备名称').fontSize(13).fontColor(COLORS.sub)
TextInput({ placeholder: '请输入设备名称' })
.fontSize(14).fontColor(COLORS.title).placeholderColor(COLORS.text3)
.backgroundColor(COLORS.chip).borderRadius(8).height(40).margin({ top: 6 })
}
.width('100%').margin({ bottom: 12 })
Column() {
Text('所在房间').fontSize(13).fontColor(COLORS.sub)
TextInput({ placeholder: '请选择房间' })
.fontSize(14).fontColor(COLORS.title).placeholderColor(COLORS.text3)
.backgroundColor(COLORS.chip).borderRadius(8).height(40).margin({ top: 6 })
}
.width('100%').margin({ bottom: 16 })
panelAdd 是添加设备弹窗。外层是 Stack 容器,底部是遮罩层,上层是弹窗内容。弹窗内容是一个纵向 Column,宽度为屏幕的 80%,深色卡片背景,圆角 20dp。
弹窗内部从上到下依次是:标题、设备名称输入框、所在房间输入框、操作按钮。
标题"➕ 添加设备"用 18号加粗白色字体,居中显示。两个表单项的结构相同——标签文字在上,输入框在下。输入框使用 ArkUI 的 TextInput 组件,绿色胶囊背景,40dp 高度,8dp 圆角。占位符文字用灰色(COLORS.text3),输入文字用白色。
Row() {
Button('取消')
.fontSize(14).fontColor(COLORS.sub).backgroundColor(COLORS.chip)
.borderRadius(20).height(40).layoutWeight(1)
.onClick(() => onClose())
Button('确认添加')
.fontSize(14).fontColor(COLORS.title).backgroundColor(COLORS.green)
.borderRadius(20).height(40).layoutWeight(1).margin({ left: 10 })
.onClick(() => onClose())
}
.width('100%')
}
.width('80%').padding(24).backgroundColor(COLORS.card).borderRadius(20)
}
.width('100%').height('100%').alignContent(Alignment.Center)
}
底部是两个并排的操作按钮:“取消"和"确认添加”。两个按钮平分宽度,都是 40dp 高度和 20dp 圆角。取消按钮用绿色胶囊背景配灰色文字,确认按钮用绿色实底配白色文字——主操作按钮用更醒目的实心背景,次操作按钮用较轻的胶囊背景,这是弹窗按钮的经典视觉区分方式。
点击两个按钮都会调用 onClose 关闭弹窗,在实际项目中,"确认添加"按钮还会执行添加设备的业务逻辑。
17.3 设备控制弹窗
@Builder panelEdit(onClose: () => void) {
Stack() {
this.modalOverlay(onClose)
Column() {
Text('✏️ 设备控制').fontSize(18).fontWeight(FontWeight.Bold).fontColor(COLORS.title).margin({ bottom: 16 })
Column() {
Text('当前设备').fontSize(13).fontColor(COLORS.sub)
Text(this.editIdx >= 0 ? DEVICE_LIST[this.editIdx % DEVICE_LIST.length].name : '—')
.fontSize(16).fontWeight(FontWeight.Bold).fontColor(COLORS.green).margin({ top: 4 })
}
.width('100%').margin({ bottom: 12 })
panelEdit 是设备控制(编辑)弹窗。与添加弹窗不同的是,这个弹窗展示的是已有设备的信息。第一栏显示"当前设备"的名称,用绿色加粗文字高亮。设备名称通过 editIdx 索引从 DEVICE_LIST 中获取,如果 editIdx 为 -1(未选中任何设备),则显示"—"。
这里有一个细节:this.editIdx % DEVICE_LIST.length 使用了取模运算来确保索引不会越界。这是一种防御性编程的写法,即使 editIdx 超出了数组范围,也能安全地取到一个有效的设备名称。
Column() {
Text('调节参数').fontSize(13).fontColor(COLORS.sub)
TextInput({ placeholder: '请输入调节数值' })
.fontSize(14).fontColor(COLORS.title).placeholderColor(COLORS.text3)
.backgroundColor(COLORS.chip).borderRadius(8).height(40).margin({ top: 6 })
}
.width('100%').margin({ bottom: 16 })
第二栏是"调节参数"输入框,用户可以在这里输入想要调节的数值(如温度、亮度等)。输入框的样式与添加弹窗一致。
Row() {
Button('取消')
.fontSize(14).fontColor(COLORS.sub).backgroundColor(COLORS.chip)
.borderRadius(20).height(40).layoutWeight(1)
.onClick(() => onClose())
Button('保存')
.fontSize(14).fontColor(COLORS.title).backgroundColor(COLORS.blue)
.borderRadius(20).height(40).layoutWeight(1).margin({ left: 10 })
.onClick(() => onClose())
}
.width('100%')
}
.width('80%').padding(24).backgroundColor(COLORS.card).borderRadius(20)
}
.width('100%').height('100%').alignContent(Alignment.Center)
}
底部的按钮是"取消"和"保存"。注意"保存"按钮的颜色是蓝色(COLORS.blue)而不是绿色——这是一个有意的设计区分:添加操作使用绿色(正向、新增),编辑保存使用蓝色(信息、确认)。这种颜色编码让用户仅凭按钮颜色就能判断操作类型。
17.4 确认移除弹窗
@Builder panelDel(onClose: () => void) {
Stack() {
this.modalOverlay(onClose)
Column() {
Text('🗑️ 确认移除').fontSize(18).fontWeight(FontWeight.Bold).fontColor(COLORS.title).margin({ bottom: 12 })
Text(this.delIdx >= 0 ? '确定要移除第 ' + (this.delIdx + 1) + ' 个设备吗?' : '确定要关闭所有设备吗?')
.fontSize(14).fontColor(COLORS.sub).textAlign(TextAlign.Center).margin({ bottom: 20 })
panelDel 是确认移除/删除弹窗。这是一个二次确认弹窗,用于危险操作的确认。标题是"🗑️ 确认移除",正文内容根据 delIdx 的值动态变化——如果有具体的索引值,就询问是否移除第 N 个设备;如果索引为 -1,就询问是否关闭所有设备。
这种"一个弹窗多种用途"的设计,在演示型应用中很常见。通过参数的不同来改变弹窗的文案和行为,可以减少重复代码。
Row() {
Button('取消')
.fontSize(14).fontColor(COLORS.sub).backgroundColor(COLORS.chip)
.borderRadius(20).height(40).layoutWeight(1)
.onClick(() => onClose())
Button('确认移除')
.fontSize(14).fontColor(COLORS.title).backgroundColor(COLORS.orange)
.borderRadius(20).height(40).layoutWeight(1).margin({ left: 10 })
.onClick(() => onClose())
}
.width('100%')
}
.width('76%').padding(24).backgroundColor(COLORS.card).borderRadius(20)
}
.width('100%').height('100%').alignContent(Alignment.Center)
}
底部按钮的主操作按钮是"确认移除",使用橙色背景(COLORS.orange)——危险操作用橙色警示,这是 UI 设计中的通用规范。橙色在整体以绿色为主的界面中非常醒目,能够有效提醒用户"这是一个危险操作,请谨慎"。
删除弹窗的宽度也比另外两个弹窗略窄一些(76% vs 80%),因为内容较少,窄一点的弹窗在视觉上更加紧凑。
17.5 弹窗系统设计总结
三套弹窗虽然功能不同,但共享了一致的设计语言:相同的遮罩层、相同的卡片圆角(20dp)、相同的按钮高度(40dp)和圆角(20dp)、相同的取消按钮样式。只有主操作按钮的颜色根据操作性质变化——添加用绿色、编辑用蓝色、删除用橙色。
这种一致性设计非常重要。它让用户在使用不同弹窗时能够形成稳定的心理模型——“左边灰色按钮是取消,右边彩色按钮是确认,按钮颜色告诉我这是什么性质的操作”。一致的交互模式能够降低用户的学习成本,提升产品的易用性。
十八、功能模块对比表
为了更清晰地展示六个 Tab 模块的特点和差异,我们从多个维度进行对比分析。
| 对比维度 | 首页(设备宫格) | 客厅(设备列表) | 卧室(场景模式) | 厨房(能耗统计) | 安防(监控卡片) | 我的(家庭管理) |
|---|---|---|---|---|---|---|
| 布局形式 | 宫格布局(2列3行) | 列表布局(单列多行) | 横向滚动卡片 | 2x2卡片网格 + 排名列表 | 列表布局 + 告警记录 | 卡片堆叠式个人中心 |
| 核心交互 | 点击卡片查看详情 | Toggle开关切换 | 点击激活场景(单选) | 数据浏览为主 | 查看/处理告警 | 列表项跳转 |
| 数据模型 | DeviceItem | LivingItem | SceneItem | EnergyItem | SecurityItem | FamilyItem |
| 数据条数 | 6个设备 | 6个设备 | 6个场景 | 4项能耗 + 5项排名 | 6个设备 + 3条告警 | 4位成员 + 4项设置 |
| 状态表达 | 图标透明度 + 状态圆点 | 图标背景色 + 边框 + 文字色 | 圆形背景 + 边框 + 文字色 + 激活标签 | 趋势箭头颜色 | statusColor三色状态 | 在线/在家圆点 |
| 特色组件 | 设备宫格卡片、快捷场景横滑 | Toggle开关、批量操作 | 大圆形场景卡、环境数据三栏 | 能耗排名进度条 | 告警等级标签、时间轴记录 | 用户统计三栏、成员列表 |
| 操作按钮 | 添加设备 | 全部开启/全部关闭 | 新建场景 | 查看详细账单 | 添加设备 | 设置列表项 |
| 使用频率 | 高(首页入口) | 高(设备控制) | 中(场景切换) | 中(数据查看) | 中低(安全检查) | 低(配置管理) |
| 信息密度 | 中等 | 高 | 低 | 高 | 高 | 中等 |
| 视觉重点 | 设备数值(暖白大字) | 开关状态(绿/灰对比) | 场景图标(大圆形象) | 能耗数值 + 进度条 | 告警状态(橙色警示) | 用户头像 + 统计数据 |
从上表可以看出,六个 Tab 的设计各有侧重,针对不同的使用场景采用了最适合的布局和交互方式。
首页和客厅是最高频使用的两个模块——首页用宫格形式快速概览所有设备状态,客厅用列表+开关提供高效的设备控制。卧室和厨房侧重于"体验"和"数据"——卧室强调场景的仪式感,厨房强调能耗的可视化。安防和我的则是工具属性较强的模块——安防关注安全状态和告警处理,我的关注账户管理和系统设置。
这种"一页面一特色"的设计思路非常值得借鉴。它避免了所有页面都用同一种布局导致的单调感,让每个 Tab 都有自己的个性和亮点。用户在不同 Tab 之间切换时,也能感受到丰富的视觉变化。
十九、总结与展望
19.1 设计亮点总结
回顾整个智能家居页面的实现,我们可以总结出以下几个突出的设计亮点。
第一,色彩体系成熟克制。 深绿(#059669)+ 暖白(#F5DEB3)+ 深色背景(#0F1419)的配色方案,在科技感和温馨感之间找到了很好的平衡点。17 种颜色变量覆盖了背景、文字、主题、点缀、功能等所有维度,且每种颜色都有明确的语义定位。统一通过 COLORS 对象管理,维护性极佳。
第二,多形态 Tab 设计。 六个 Tab 采用了六种完全不同的布局——宫格、列表、横滑卡片、数据卡片、监控卡片、个人中心。这种多样性展示了 ArkUI 在复杂页面构建上的灵活性,也让应用的功能层次更加丰富。每个 Tab 都有自己的设计重点和交互特色,用户不会感到单调。
第三,状态可视化细致入微。 从设备的开/关状态,到安防的在线/离线/告警三态,再到场景的激活态、成员的在家/外出状态,所有的状态变化都有对应的视觉反馈。颜色、透明度、背景色、边框、标签等多种手段组合运用,让状态信息一目了然。statusColor 和 deviceColor 两个辅助函数则将状态映射逻辑集中管理,体现了良好的代码组织能力。
第四,呼吸动画增添活力。 通过 setInterval + breath 布尔值翻转的方式,实现了头部能耗数值的颜色呼吸和图表柱状图的高度呼吸。这两个微动效虽然简单,却为整个界面注入了生命力,让静态的数据变得"活"了起来。在不引入复杂动画系统的前提下,用最小的成本获得了不错的视觉效果。
第五,弹窗系统设计规范。 三套弹窗共享遮罩层和基本样式,主操作按钮的颜色根据操作性质区分(绿/蓝/橙),形成了一套完整的弹窗设计规范。一致的交互模式降低了用户的学习成本,也体现了设计的系统性思维。
第六,数据驱动的开发模式。 所有的 Tab 配置、设备数据、场景数据都以数组的形式集中定义,UI 通过 ForEach 遍历数据生成。这种"数据驱动视图"的开发模式是 ArkUI 声明式编程的核心理念——数据是单一数据源,UI 是数据的映射。修改数据就能改变 UI,代码逻辑清晰,维护成本低。
19.2 技术实现要点
从技术实现的角度,该源码也有许多值得学习的地方。
@Observed 与 @State 的配合使用。 六个数据模型类都用 @Observed 装饰,组件内部用 @State 装饰数组变量。这种组合使得对象内部的属性变化(如 livingList[idx].isOn = isOn)也能触发 UI 刷新。这是 ArkUI 响应式系统中一个容易被忽视但非常重要的细节。
@Builder 函数的合理拆分。 整个页面的 UI 构建被拆分成了十多个 @Builder 函数——头部一个、六个 Tab 各一个、图表和 Tab 栏各一个、弹窗四个、还有设备卡片和能耗卡片两个可复用小组件。每个函数职责单一,代码可读性好。这种按功能模块拆分 Builder 函数的做法,是复杂页面管理的最佳实践。
条件渲染的灵活运用。 if (this.currentTab === 0) 这样的条件渲染实现了 Tab 切换;if (item.active) 实现了激活标签的条件显示;if (item.status === '告警') 实现了告警按钮的条件显示。ArkUI 的条件渲染语法简洁直观,与 TypeScript 的控制流无缝衔接。
手动分行的网格布局。 设备宫格和能耗卡片都采用了"手动定义行索引数组 + 每行一个 Row"的方式实现两列布局。这种方式虽然在数据量大的场景下不太灵活,但对于固定数量的展示型应用来说,布局控制精确、代码结构清晰,是一种简单有效的实现方式。
19.3 优化与扩展建议
尽管当前的实现已经相当完善,但从实际生产应用的角度,还有以下几个方向可以进一步优化和扩展。
其一,引入更完善的状态管理。 当前所有的状态都集中在组件内部的 @State 变量中,对于单页面应用来说问题不大。但如果应用规模扩大、页面增多,就需要考虑引入全局状态管理方案,如 ArkUI 的 AppStorage、LocalStorage,或者第三方的状态管理库。将设备数据、场景数据等提升到全局状态,可以实现跨页面的数据共享。
其二,增加动画过渡效果。 当前 Tab 切换是瞬间完成的,没有过渡动画。可以使用 ArkUI 的 animateTo 或组件转场动画,为 Tab 切换添加淡入淡出、左右滑动等过渡效果,提升交互的流畅感。场景激活、设备开关等状态变化也可以添加属性动画,让界面变化更加顺滑。
其三,完善弹窗的表单验证。 当前添加和编辑弹窗的输入框没有做任何校验。在实际项目中,需要增加表单验证逻辑——设备名称不能为空、参数数值必须在有效范围内等。验证失败时给出明确的错误提示,确保数据的合法性。
其四,接入真实的后端数据。 目前所有数据都是硬编码的常量。在实际项目中,需要通过 HTTP 请求或 WebSocket 从后端获取实时数据。可以将数据获取逻辑封装在 Service 层,与 UI 层解耦,便于后续的维护和测试。
其五,增加更多的设备控制能力。 当前设备的控制方式比较单一,主要是开关切换和简单的数值输入。可以增加更多形式的控制组件,如 Slider 滑块调节亮度/温度、ColorPicker 选择灯光颜色、ListPicker 选择模式等,让设备控制更加丰富和直观。
其六,支持设备的增删改查。 当前添加和删除操作只是弹出了弹窗,并没有真正修改数据列表。在实际项目中,需要实现完整的 CRUD 操作——添加设备后将新设备加入列表、删除设备后从列表中移除、编辑设备后更新对应的数据项。
其七,增加数据持久化。 可以使用 HarmonyOS 的首选项(Preferences)或关系型数据库(RelationalStore)将设备列表、场景配置等数据持久化到本地,确保应用重启后数据不会丢失。
其八,适配多设备和多分辨率。 HarmonyOS 的一大优势是跨设备能力。可以进一步优化布局的响应式能力,让页面在手机、平板、智慧屏等不同尺寸的设备上都能有良好的显示效果。使用栅格布局(GridRow/GridCol)或相对布局可以更好地适配多种屏幕尺寸。
19.4 结语
智能家居是物联网时代最具想象力的应用场景之一,而优秀的 UI 交互设计则是智能家居体验的关键一环。通过对这款"智家"应用的源码分析,我们不仅学习了 ArkUI 框架的具体用法,更领略了智能家居 UI 设计的思路和方法。
从色彩体系到数据模型,从组件结构到每个 Tab 的细节设计,从图表动效到弹窗系统,该源码展示了一个完整的智能家居应用页面是如何从零开始构建的。深绿与暖白的配色营造出温馨的家居氛围,六种不同布局的 Tab 展现了丰富的功能层次,呼吸动画和状态反馈为界面注入了灵动的生命力。
ArkUI 的声明式开发模式让复杂的 UI 构建变得简洁而优雅——描述状态,框架自动渲染。@Observed、@State、@Builder 这些装饰器和语法特性,虽然每一个看起来都很简单,但组合在一起却能构建出功能完整、交互丰富的应用界面。这正是声明式 UI 的魅力所在。
随着 HarmonyOS 生态的不断发展和智能家居设备的日益普及,我们有理由相信,未来会有更多优秀的智能家居应用涌现出来。而 ArkUI 作为 HarmonyOS 的原生 UI 框架,也将在这个过程中扮演越来越重要的角色。希望这篇源码分析能够为正在从事或即将进入智能家居开发领域的开发者提供一些参考和启发。
附录:DevEco Studio 创建新项目与查看 SDK 版本
本章节演示如何使用 DevEco Studio 创建一个 HarmonyOS 新项目,并查看当前 IDE 已安装的 SDK 版本,适合作为其他技术博文的补充操作指南。
一、创建新项目
1.1 进入欢迎界面
启动 DevEco Studio 后,首先看到的是欢迎界面。左侧导航栏默认选中 “项目”,右侧提供三个主要入口:
- 新建项目:从头创建新项目
- 打开项目:打开本地已有项目
- 克隆仓库:从 Git 等版本控制拉取代码
点击 “新建项目” 按钮,进入项目创建向导。

1.2 选择项目模板
在弹出的"新建项目"对话框中,左侧分类标签提供了两种项目类型:
| 类型 | 说明 |
|---|---|
| 应用(Application) | 开发标准的 HarmonyOS 应用,具备完整的 Ability 生命周期 |
| 元服务(Atomic Service) | 开发轻量级的原子化服务,无需安装即可使用 |
选择 “应用” 标签后,右侧展示多种模板。对于大多数场景,推荐选择 “Empty Ability” —— 这是一个最基础的入门模板,仅包含 Hello World 功能,适合从零开始构建应用。

1.3 配置项目信息
点击 “下一步” 后,进入项目配置界面,需要填写以下核心参数:
| 配置项 | 示例值 | 说明 |
|---|---|---|
| 项目名称(Project name) | rollboat |
应用的项目名称,建议使用英文命名 |
| 包名(Bundle name) | com.rollboat.myapplication |
应用唯一标识,采用反向域名格式 |
| 保存路径(Save location) | D:\CodeFactory\rollboat |
项目本地存储路径,避免使用中文和空格 |
| 兼容 SDK(Compatible SDK) | 6.1.1(24) |
目标 HarmonyOS API 版本,点击"查看参考"可了解各版本差异 |
| 模块名称(Module name) | entry |
主模块名称,默认 entry 为应用入口模块 |
| 设备类型(Device types) | ☑ Phone | 勾选目标设备:Phone / Tablet / 2in1 / Car / Wearable / TV |
右侧预览区会实时展示当前模板的默认效果 —— 一个居中显示的 “Hello World” 文本。

1.4 完成创建
确认配置无误后,点击右下角 “完成” 按钮,IDE 将自动执行以下操作:
- 生成项目骨架(Stage 模型目录结构)
- 执行
ohpm install安装依赖 - 运行 Hvigor 构建初始化(
Build Init)
构建日志中显示 “退出代码为 0” 表示项目初始化成功。

1.5 项目结构概览
创建完成后,左侧项目面板展示的是标准的 Stage 模型 目录结构:
rollboat/
├── .hvigor/ # Hvigor 构建工具缓存
├── .idea/ # IDE 配置文件
├── AppScope/ # 应用级全局配置
│ └── app.json5
├── entry/ # 主模块(入口模块)
│ ├── src/main/ets/
│ │ ├── entryability/ # Ability 生命周期管理
│ │ │ └── EntryAbility.ets
│ │ └── pages/ # UI 页面
│ │ └── Index.ets # 首页(默认 Hello World)
│ ├── src/main/resources/ # 资源文件
│ ├── module.json5 # 模块配置
│ └── build-profile.json5 # 构建配置
├── oh_modules/ # OHPM 依赖包
├── build-profile.json5 # 工程构建配置
├── hvigorfile.ts # Hvigor 构建脚本
└── oh-package.json5 # 包管理配置
核心文件 Index.ets 的默认代码如下,采用 ArkTS 声明式 UI 语法:
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
RelativeContainer() {
Text(this.message)
.id('HelloWorld')
.fontSize($r('app.float.page_text_font_size'))
.fontWeight(FontWeight.Bold)
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
.onClick(() => {
this.message = 'Welcome';
})
}
.height('100%')
.width('100%')
}
}
| 关键语法 | 作用 |
|---|---|
@Entry |
标记为页面入口,可用于路由跳转 |
@Component |
声明为自定义组件 |
@State |
状态变量,数据变更时自动触发 UI 刷新 |
RelativeContainer |
相对布局容器,替代传统线性布局 |
.onClick() |
点击事件,此处点击后文本变为 “Welcome” |
打开右侧 Previewer(预览器),选择 Phone 设备,即可实时预览 Hello World 效果,无需连接真机或启动模拟器。

二、查看 SDK 版本
2.1 查看 HarmonyOS SDK
DevEco Studio 安装时已内置 HarmonyOS SDK,无需单独下载。通过以下路径查看:
文件 → 设置 → HarmonyOS SDK(或快捷键
Ctrl + Alt + S搜索 “HarmonyOS SDK”)
在设置面板中,可以看到当前已安装的 SDK 版本信息:
| 名称 | 阶段 | 状态 |
|---|---|---|
| HarmonyOS 6.1.1 | Release | ✅ 已安装 |
界面顶部提示:“HarmonyOS SDK 已经包含在 IDE,无需单独安装”,省去了手动配置 SDK 的繁琐步骤。

2.2 查看 ArkUI-X SDK(跨平台扩展)
如果项目需要将 ArkUI 框架扩展到多个 OS 平台(Android / iOS / OpenHarmony),还需要配置 ArkUI-X SDK。路径如下:
文件 → 设置 → 语言和框架 → ArkUI-X
在这里可以查看已安装和可选的 ArkUI-X SDK 版本:
| 版本 | SDK 版本号 | 阶段 | 状态 |
|---|---|---|---|
| API Version 24 | 6.1.1.100 | Release | ✅ 已安装 |
| API Version 23 | 6.1.0.28 | Beta1 | 未安装 |
| API Version 22 | 6.0.2.112 | Release | 未安装 |
安装路径示例:D:\DevTools\ArkUI-X\sdk
说明:ArkUI-X 允许开发者使用一套 ArkTS 主代码,同时构建多平台应用。如果仅开发 HarmonyOS 原生应用,无需额外安装 ArkUI-X SDK。

三、小结
| 步骤 | 操作 | 关键点 |
|---|---|---|
| 创建项目 | 欢迎页 → 新建项目 → 选择 Empty Ability 模板 → 配置项目信息 → 完成 | 使用 Stage 模型 + ArkTS 语言 |
| 查看 SDK | 设置 → HarmonyOS SDK | SDK 已内置,无需手动安装 |
| 跨平台扩展 | 设置 → ArkUI-X | 根据需要安装对应 API 版本 |
至此,DevEco Studio 的项目创建与 SDK 环境确认全部完成,可以开始 HarmonyOS 应用的功能开发。
本文基于 DevEco Studio 6.1.1 Release 版本编写,不同版本界面可能存在细微差异。
更多推荐



所有评论(0)