@Env 环境变量 自学指南:玩转局部主题和局部深浅色
在 ArkUI 里,除了 它的作用可以简单理解为: 这篇文章就带你从 0 上手 官方定义: 基础用法长这样: 这里有三件事: 也就是说: 你平时用到的就是这个形式: 当前文档里只暴露了一个枚举值: 说明: 简单理解: 常见触发方式: 排查建议: 一定要使用 先来一个最简单的 Demo:把断点信息打印出来,方便你在真机/模拟器上看效果。 建议你: 下面是一个「手机一列、大屏两列」的简化示例。重点是思路,你可以根据实际字段名调整判断逻辑。 上面例子里,有几点可以参考到自己的项目里: 虽然语法上你可以给字段重新赋值,但语义上 @Env 注入的是环境变量: 通常来说, 可以简单记一个心法: 典型场景包括: 响应式布局: 窗口多实例 / 多窗口: 元服务 / 卡片场景: 掌握它之后,你可以把「环境感知」这件事,从零散的 @State、@Prop 这些状态/属性装饰器之外,还有一个很偏底层、但非常好用的能力:@Env 环境变量装饰器。把系统/运行环境的一些“全局状态”,以属性的形式注入到组件里,让 UI 能“感知环境变化”。
@Env,并给出一个可直接改造进项目的示例。一、@Env 是什么?能做什么?
SystemCapability.ArkUI.ArkUI.Full;Env 这个装饰器,用来把系统环境变量注入 ArkUI 组件字段。import { uiObserver } from '@kit.ArkUI';
@Entry
@Component
struct Index {
@Env(SystemProperties.BREAK_POINT)
breakpoint: uiObserver.WindowSizeLayoutBreakpointInfo;
build() {
// 根据 breakpoint 做自适应布局
}
}@Env(...) 装饰组件字段;SystemProperties 枚举值(环境变量的“key”);BREAK_POINT 对应 WindowSizeLayoutBreakpointInfo。✅ 重点:当
@Env 写在 @Component / @ComponentV2 内部字段上时,它能拿到当前窗口的一些环境信息,而不是全局单例。二、核心类型:EnvDecorator & SystemProperties
2.1 EnvDecorator 类型定义
declare type EnvDecorator = (value: SystemProperties) => PropertyDecorator;Env 自己就是一个函数;SystemProperties;PropertyDecorator,用于修饰组件字段。@Env(SystemProperties.BREAK_POINT)
breakpoint: uiObserver.WindowSizeLayoutBreakpointInfo;2.2 SystemProperties 枚举
enum SystemProperties {
BREAK_POINT = 'system.arkui.breakpoint'
}BREAK_POINT:通过 @Env(SystemProperties.BREAK_POINT) 能获取到一个uiObserver.WindowSizeLayoutBreakpointInfo 实例;@Component / @ComponentV2 里时,用来获取当前自定义组件所在窗口的尺寸布局断点信息。这个 breakpoint 可以用来做「手机/平板/大屏」之类的响应式 UI 控制逻辑。
三、错误码:140000 如何排查?
@Env 只有一个官方错误码,非常好记:错误码 ID 错误信息 含义 140000 Invalid key for @Env 传给 @Env(...) 的 key 不合法(不是支持的 SystemProperties)// ❌ 错误示例:写了不存在的 key
@Env('system.arkui.xx' as any)
env: any;SystemProperties 枚举,不要手写字符串:@Env(SystemProperties.BREAK_POINT)
breakpoint: uiObserver.WindowSizeLayoutBreakpointInfo;SystemProperties。四、最小可运行示例:打印窗口断点信息
import { uiObserver } from '@kit.ArkUI';
@Entry
@Component
struct BreakpointDemo {
@Env(SystemProperties.BREAK_POINT)
breakpoint: uiObserver.WindowSizeLayoutBreakpointInfo;
build() {
Column() {
Text('当前窗口断点信息:')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 8 })
// 简单直接:把对象序列化出来看
Text(JSON.stringify(this.breakpoint))
.fontSize(14)
.fontColor('#999999')
.lineHeight(18)
.textAlign(TextAlign.Start)
.margin({ left: 12, right: 12 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}JSON.stringify(this.breakpoint) 输出的字段结构;⚠️ 注意:
WindowSizeLayoutBreakpointInfo 的字段以当前 SDK 官方文档为准,这里用 JSON.stringify 的方式,就是为了避免你一开始就被字段名卡住。五、实战:用 @Env 写一个响应式布局
5.1 思路设计
@Env(SystemProperties.BREAK_POINT) 拿到断点信息;build() 里根据这些参数布局内容。5.2 示例代码(判断逻辑示意)
import { uiObserver } from '@kit.ArkUI';
@Entry
@Component
struct ResponsiveGridPage {
@Env(SystemProperties.BREAK_POINT)
breakpoint: uiObserver.WindowSizeLayoutBreakpointInfo;
// 根据断点信息,推导当前列数(伪代码,具体判断按实际字段改)
private get columnCount(): number {
// 根据实际字段来写,比如 this.breakpoint.windowSizeClass / width / type 等等
// 这里用伪逻辑举例:
// - 小屏:1 列
// - 中屏及以上:2 列
// 请结合自己工程中的 WindowSizeLayoutBreakpointInfo 实际字段来判断
try {
// 你可以先打印 breakpoint 再决定判断方式
return this.isLargeLike() ? 2 : 1;
} catch (e) {
// 容错:拿不到断点时,降级为 1 列
return 1;
}
}
private isLargeLike(): boolean {
// 这里仅示意:真实项目里用宽度、sizeClass 等字段来判断
// 比如:
// return this.breakpoint.width >= 600;
console.info('breakpoint:', JSON.stringify(this.breakpoint));
return false;
}
build() {
Column() {
Text('响应式布局示例(基于 @Env 断点)')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 12 })
// 简单模拟一个“宫格列表”
this.buildGrid()
}
.width('100%')
.height('100%')
.padding(16)
}
private buildGrid() {
// 为了示例简单,这里模拟 6 个 Item
const items: number[] = [1, 2, 3, 4, 5, 6];
if (this.columnCount === 1) {
// 一列:竖向列表
Column({ space: 8 }) {
ForEach(items, (item: number) => {
this.buildCard(item)
})
}
} else {
// 两列:简单两列栅格(更复杂的可以用自定义布局组件)
Column({ space: 8 }) {
ForEach(this.splitToRows(items, 2), (row: number[], index: number) => {
Row({ space: 8 }) {
ForEach(row, (item: number) => {
// 每列占据一半空间
this.buildCard(item)
.layoutWeight(1)
})
}
})
}
}
}
// 工具:把一维数组拆成二维
private splitToRows(list: number[], count: number): number[][] {
const result: number[][] = [];
let temp: number[] = [];
list.forEach((v, i) => {
temp.push(v);
if (temp.length === count || i === list.length - 1) {
result.push(temp);
temp = [];
}
});
return result;
}
private buildCard(index: number) {
return Column() {
Text(`Card ${index}`)
.fontSize(16)
.fontWeight(FontWeight.Medium)
Text('这里是内容区域,可以放图片、标题、按钮等。')
.fontSize(12)
.fontColor('#999999')
.margin({ top: 4 })
}
.padding(12)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.shadow({ radius: 8, color: '#22000000', offsetY: 2 })
}
}@Env(...) 注入的环境变量封装成 getter/方法;columnCount 的计算逻辑。六、@Env 使用注意事项
6.1 只能装饰属性,且用在组件里才有意义
@Env 是装饰字段的,不是方法;@Component / @ComponentV2 内部;6.2 环境变量是“只读语义”
this.breakpoint = xxx 去修改系统状态。6.3 响应性 & 性能
@Env 注入的变量会随环境变化(比如窗口尺寸变更)而更新,你可以:build() 或 getter 里使用;aboutToAppear 中打印一次,了解变化频率,再做优化。七、什么时候应该用 @Env?
当你写 UI 时,发现需要「感知设备 /窗口环境」时,就可以想一想:能不能用 @Env?
@Env 拿到差异,裁剪 UI。八、总结
@Env 看起来只是一个小小的装饰器,但定位其实很清晰:@State / @Prop 管组件内部/外部数据;@Env 管组件所处的“环境维度”的信息。getWindowRect、全局单例逻辑中抽离出来,用更声明式、更 ArkUI 风格的写法来组织代码。