











弃用提醒 全局方法 vp2px()、px2vp() 等单位转换API已被标记为弃用,API 12及以上版本推荐通过UIContext实例调用转换方法。
传统全局单位转换方法的根本问题在于上下文归属不清,在复杂场景下会导致转换结果不准确:
• 多窗口场景:应用同时显示多个窗口时,不同窗口可能运行在不同屏幕密度的显示器上,全局方法无法区分窗口归属
• 多UI实例:折叠屏、分屏等场景下,同一应用可能有多个UI实例,全局方法无法获取正确的实例参数
• 异步回调:在异步回调中调用全局方法时,当前UI上下文可能已经发生变化
• 跨设备迁移:分布式场景下,应用迁移到其他设备时屏幕参数变化,全局方法无法实时适配
以下全局单位转换方法均已被标记为弃用:
|
弃用方法 |
功能说明 |
替代方案 |
|
vp2px(vpValue: number): number |
vp单位转像素px |
uiContext.vp2px(vpValue) |
|
px2vp(pxValue: number): number |
像素px转vp单位 |
uiContext.px2vp(pxValue) |
|
fp2px(fpValue: number): number |
字体单位fp转像素px |
uiContext.fp2px(fpValue) |
|
px2fp(pxValue: number): number |
像素px转字体单位fp |
uiContext.px2fp(pxValue) |
|
lpx2px(lpxValue: number): number |
逻辑像素lpx转像素px |
uiContext.lpx2px(lpxValue) |
|
px2lpx(pxValue: number): number |
像素px转逻辑像素lpx |
uiContext.px2lpx(pxValue) |
技术背景
UIContext代表具体UI实例的运行时环境,包含了该UI所属窗口的屏幕密度、显示参数等信息,基于UIContext的转换能确保结果始终与当前显示环境匹配。
从API 12版本开始,官方推荐通过UIContext实例调用单位转换方法,根据代码所在场景的不同,有以下几种实现方式:
在自定义组件中通过this.getUIContext()直接获取当前组件的UI上下文:
import { UIContext } from '@kit.ArkUI';
@Entry
@Component
struct ExampleComponent {
build() {
Column({ space: 20 }) {
Button('vp转px示例')
.width(200)
.height(40)
.onClick(() => {
// ✅ 正确:通过组件实例获取UIContext
const uiContext: UIContext = this.getUIContext();
const widthInPx: number = uiContext.vp2px(100); // 将100vp转换为px
const heightInVp: number = uiContext.px2vp(200); // 将200px转换为vp
console.log(`100vp = ${widthInPx}px`);
console.log(`200px = ${heightInVp}vp`);
})
Button('fp转换示例')
.width(200)
.height(40)
.onClick(() => {
const uiContext: UIContext = this.getUIContext();
const fontSizePx: number = uiContext.fp2px(16); // 16fp转px
console.log(`16fp = ${fontSizePx}px`);
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
在Ability生命周期中,需要在页面内容加载完成后,从主窗口获取UIContext:
import { UIAbility } from '@kit.AbilityKit';
import { window, UIContext } from '@kit.ArkUI';
import type AbilityConstant from '@ohos.app.ability.AbilityConstant';
import type Want from '@ohos.app.ability.Want';
export default class EntryAbility extends UIAbility {
private uiContext?: UIContext;
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
console.log('Ability创建');
}
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
console.error('页面加载失败', JSON.stringify(err));
return;
}
// ✅ 正确:在loadContent回调中获取主窗口的UIContext
try {
const mainWindow = windowStage.getMainWindowSync();
this.uiContext = mainWindow.getUIContext();
if (this.uiContext) {
// 单位转换示例
const designWidthVp = 360; // 设计稿宽度vp
const screenWidthPx = this.uiContext.vp2px(designWidthVp);
console.info(`设计稿宽度${designWidthVp}vp对应实际像素${screenWidthPx}px`);
// 全局初始化适配参数
AppStorage.setOrCreate('screenWidthPx', screenWidthPx);
}
} catch (error) {
console.error('获取UIContext失败', JSON.stringify(error));
}
});
}
}
对于无法直接获取UIContext的工具类或模块,最稳妥的做法是将UIContext作为参数从外部传入:
// UI适配工具类
export class UIAdaptUtil {
/**
* 根据设计稿尺寸计算实际显示宽度
* @param uiContext UI上下文实例
* @param designVp 设计稿vp值
* @returns 实际像素值
*/
static getRealWidth(uiContext: UIContext, designVp: number): number {
if (!uiContext) {
console.warn('UIContext为空,返回默认值');
return designVp; // 降级处理
}
return uiContext.vp2px(designVp);
}
/**
* 适配字体大小
* @param uiContext UI上下文实例
* @param designFp 设计稿fp值
* @returns 实际像素值
*/
static getFontSize(uiContext: UIContext, designFp: number): number {
if (!uiContext) {
return designFp;
}
return uiContext.fp2px(designFp);
}
}
// 在组件中调用工具类
@Entry
@Component
struct ToolDemoPage {
build() {
Column() {
Text('适配示例')
.fontSize(UIAdaptUtil.getFontSize(this.getUIContext(), 18)) // 传入当前UIContext
Button('计算宽度')
.onClick(() => {
const realWidth = UIAdaptUtil.getRealWidth(this.getUIContext(), 200);
console.log('实际宽度', realWidth);
})
}
.width('100%')
.height('100%')
}
}
禁止行为
❌ 禁止在工具类内部使用全局方法获取UIContext
❌ 禁止缓存UIContext实例长期使用,可能导致上下文失效
✅ 推荐每次调用时从调用方传入最新的UIContext实例
为简化调用,建议封装统一的单位转换工具类,统一处理异常和降级逻辑:
// UnitConvertUtil.ets
import { UIContext } from '@kit.ArkUI';
/**
* 单位转换工具类
* 统一封装所有单位转换方法,处理异常情况
*/
export class UnitConvertUtil {
/**
* vp转px
* @param uiContext UI上下文
* @param vpValue vp值
* @param defaultValue 转换失败时的默认值
* @returns 像素值
*/
static vp2px(uiContext: UIContext | null | undefined, vpValue: number, defaultValue: number = vpValue): number {
if (!uiContext) {
console.warn(`vp2px转换失败,UIContext为空,vpValue=${vpValue},返回默认值${defaultValue}`);
return defaultValue;
}
try {
return uiContext.vp2px(vpValue);
} catch (error) {
console.error(`vp2px转换异常,vpValue=${vpValue},error=${JSON.stringify(error)}`);
return defaultValue;
}
}
/**
* px转vp
* @param uiContext UI上下文
* @param pxValue 像素值
* @param defaultValue 转换失败时的默认值
* @returns vp值
*/
static px2vp(uiContext: UIContext | null | undefined, pxValue: number, defaultValue: number = pxValue): number {
if (!uiContext) {
console.warn(`px2vp转换失败,UIContext为空,pxValue=${pxValue},返回默认值${defaultValue}`);
return defaultValue;
}
try {
return uiContext.px2vp(pxValue);
} catch (error) {
console.error(`px2vp转换异常,pxValue=${pxValue},error=${JSON.stringify(error)}`);
return defaultValue;
}
}
/**
* fp转px
* @param uiContext UI上下文
* @param fpValue fp值
* @param defaultValue 转换失败时的默认值
* @returns 像素值
*/
static fp2px(uiContext: UIContext | null | undefined, fpValue: number, defaultValue: number = fpValue): number {
if (!uiContext) {
console.warn(`fp2px转换失败,UIContext为空,fpValue=${fpValue},返回默认值${defaultValue}`);
return defaultValue;
}
try {
return uiContext.fp2px(fpValue);
} catch (error) {
console.error(`fp2px转换异常,fpValue=${fpValue},error=${JSON.stringify(error)}`);
return defaultValue;
}
}
}
// 使用示例
@Entry
@Component
struct UtilDemoPage {
build() {
Column() {
Text('封装工具类示例')
.fontSize(UnitConvertUtil.fp2px(this.getUIContext(), 16))
.width(UnitConvertUtil.vp2px(this.getUIContext(), 200))
.height(UnitConvertUtil.vp2px(this.getUIContext(), 40))
.backgroundColor('#f5f5f5')
.textAlign(TextAlign.Center)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
UIContext的获取需要注意调用时机,避免在UI实例未准备好时调用:
|
调用场景 |
是否推荐 |
说明 |
|
用户交互回调(onClick等) |
✅ 推荐 |
UI已完全渲染,上下文稳定 |
|
onPageShow生命周期 |
✅ 推荐 |
页面已显示,上下文可用 |
|
onAreaChange回调 |
✅ 推荐 |
组件布局已完成,尺寸稳定 |
|
aboutToAppear生命周期 |
❌ 不推荐 |
组件尚未挂载到组件树,上下文可能未准备好 |
|
build()方法中直接调用 |
❌ 禁止 |
构建过程中上下文不稳定,可能导致异常 |
|
异步回调中使用 |
⚠️ 谨慎使用 |
回调时UI上下文可能已变化,建议提前获取UIContext |
• 避免重复计算:在列表滚动、onAreaChange等频繁回调中,避免重复进行单位转换,提前计算好转换值并缓存
• 批量转换:需要转换多个值时,复用同一个UIContext实例,避免重复获取
• 常量提前转换:固定不变的尺寸值在页面初始化时一次性转换,避免每次build都计算
• 避免频繁调用:单位转换涉及浮点运算,高频调用会影响性能,尽量减少不必要的转换
// ✅ 优化示例:提前缓存转换结果
@Entry
@Component
struct PerformanceDemoPage {
@State itemWidth: number = 0;
@State itemHeight: number = 0;
@State fontSize: number = 0;
onPageShow() {
// 页面显示时一次性计算所有需要的转换值
const uiContext = this.getUIContext();
this.itemWidth = uiContext.vp2px(160);
this.itemHeight = uiContext.vp2px(200);
this.fontSize = uiContext.fp2px(14);
}
build() {
Grid() {
LazyForEach(this.dataSource, (item: ItemData) => {
GridItem() {
Column() {
Image(item.imageUrl)
.width(this.itemWidth)
.height(this.itemHeight - 30) // 使用缓存的值,不需要重复转换
Text(item.name)
.fontSize(this.fontSize) // 使用缓存的值
.width(this.itemWidth)
}
}
})
}
.columnsTemplate('1fr 1fr')
// 滚动过程中不需要重复计算单位
}
}
|
问题现象 |
原因分析 |
解决方案 |
|
调用getUIContext返回undefined或null |
调用时机过早,UI实例尚未创建完成 |
延迟到onPageShow或用户交互时获取,或从窗口实例获取 |
|
单位转换结果不准确,尺寸显示异常 |
使用了全局转换方法,或使用了错误的UIContext实例 |
迁移到UIContext方式,确保使用当前组件对应的UIContext |
|
多窗口下尺寸显示不一致 |
全局方法无法区分不同窗口的屏幕参数 |
每个窗口使用自己的UIContext进行转换 |
|
折叠屏展开/折叠后尺寸错乱 |
缓存了旧的转换结果,屏幕参数变化后未更新 |
监听屏幕尺寸变化事件,重新计算转换值 |
|
应用迁移到其他设备后尺寸异常 |
使用了固定像素值或缓存的转换结果 |
迁移完成后重新获取UIContext进行转换,适配新设备参数 |
|
转换时报错"context is destroyed" |
UI实例已销毁,UIContext已失效 |
添加异常捕获,避免在页面销毁后调用转换方法 |
1. ✅ 所有全局单位转换方法已替换为UIContext方式
2. ✅ UIContext的获取时机正确,不在aboutToAppear和build中直接调用
3. ✅ 非UI模块通过参数传入UIContext,不自行获取
4. ✅ 转换方法添加了异常捕获和降级处理
5. ✅ 频繁调用场景下已缓存转换结果,避免重复计算
6. ✅ 多窗口、折叠屏等场景已测试,转换结果正确
7. ✅ 没有长期缓存UIContext实例,每次使用时重新获取
8. ✅ 屏幕尺寸变化时重新计算相关转换值
迁移建议
存量项目可以逐步迁移,先替换核心业务模块的转换逻辑,再逐步覆盖全量代码。DevEco Studio会对已弃用的全局方法显示警告提示,可通过警告快速定位需要修改的代码。
文档版本:V1.0 | 适配版本:HarmonyOS NEXT API 12+ | 更新日期:2024年4月
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。