像素转换案例实战

article/2025/7/12 23:00:42

本案例介绍像素单位的基本知识与像素单位转换API的使用。通过像素转换案例,向开发者讲解了如何使用像素单位设置组件的尺寸、字体的大小以及不同像素单位之间的转换方法。主要功能包括:

  1. 展示了不同像素单位的使用。
  2. 展示了像素单位转换相关API的使用。

一、学习像素单位

ArkUI为开发者提供4种像素单位,框架采用vp为基准数据单位。

名称

描述

px

屏幕物理像素单位。

vp

屏幕密度相关像素,根据屏幕像素密度转换为屏幕物理像素,当数值不带单位时,默认单位vp。

说明:

vp与px的比例与屏幕像素密度有关。

fp

字体像素,与vp类似适用屏幕密度变化,随系统字体大小设置变化。

lpx

视窗逻辑像素单位,lpx单位为实际屏幕宽度与逻辑宽度(通过 designWidth 配置)的比值,designWidth默认值为720。当 designWidth 为 720 时,在实际宽度为 1440 物理像素的屏幕上,1lpx为2px大小。

像素单位转换

提供其他单位与px单位互相转换的方法。

接口

描述

vp2px(value : number) : number

将vp单位的数值转换为以px为单位的数值。

说明:

默认使用当前UI实例所在屏幕的虚拟像素比进行转换,UI实例未创建时,使用默认屏幕的虚拟像素比进行转换。

px2vp(value : number) : number

将px单位的数值转换为以vp为单位的数值。

说明:

默认使用当前UI实例所在屏幕的虚拟像素比进行转换,UI实例未创建时,使用默认屏幕的虚拟像素比进行转换。

fp2px(value : number) : number

将fp单位的数值转换为以px为单位的数值。

px2fp(value : number) : number

将px单位的数值转换为以fp为单位的数值。

lpx2px(value : number) : number

将lpx单位的数值转换为以px为单位的数值。

px2lpx(value : number) : number

将px单位的数值转换为以lpx为单位的数值。

示例

说明

直接使用vp2px/px2vp/fp2px/px2fp/lpx2px/px2lpx可能存在UI上下文不明确的问题,建议使用getUIContext获取UIContext实例,再使用UIContext下的vp2px/px2vp/fp2px/px2fp/lpx2px/px2lpx调用绑定实例的接口。

PixelConversionBasic

// ets/pages/Index.ets@Entry
@Component
struct Example {build() {Column() {Column() {Column() {Text("width(220)").width(220).height(40).backgroundColor(0x6435c9).textAlign(TextAlign.Center).fontColor(Color.White).fontSize('12vp')}.margin(5)Column() {Text("width('220px')").width('220px').height(40).backgroundColor(0x6435c9).textAlign(TextAlign.Center).fontColor(Color.White)}.margin(5)Column() {Text("width('220vp')").width('220vp').height(40).backgroundColor(0x6435c9).textAlign(TextAlign.Center).fontColor(Color.White).fontSize('12vp')}.margin(5)Column() {Text("width('220lpx') designWidth:720").width('220lpx').height(40).backgroundColor(0x6435c9).textAlign(TextAlign.Center).fontColor(Color.White).fontSize('12vp')}.margin(5)Column() {Text("width(vp2px(220) + 'px')")// 建议使用this.getUIContext().vp2px().width(vp2px(220) + 'px').height(40).backgroundColor(0x6435c9).textAlign(TextAlign.Center).fontColor(Color.White).fontSize('12vp')}.margin(5)Column() {Text("fontSize('12fp')").width(220).height(40).backgroundColor(0x6435c9).textAlign(TextAlign.Center).fontColor(Color.White).fontSize('12fp')}.margin(5)Column() {Text("width(px2vp(220))")// 建议使用this.getUIContext().px2vp().width(px2vp(220)).height(40).backgroundColor(0x6435c9).textAlign(TextAlign.Center).fontColor(Color.White).fontSize('12fp')}.margin(5)}.width('100%')}}
}

二、学习组件导航 (Navigation)

Navigation是路由导航的根视图容器,一般作为页面(@Entry)的根容器,包括单栏(Stack)、分栏(Split)和自适应(Auto)三种显示模式。Navigation组件适用于模块内和跨模块的路由切换,通过组件级路由能力实现更加自然流畅的转场体验,并提供多种标题栏样式来呈现更好的标题和内容联动效果。一次开发、多端部署场景下,Navigation组件能够自动适配窗口显示大小,在窗口较大的场景下自动切换分栏展示效果。

Navigation组件主要包含导航页(NavBar)和子页(NavDestination)。导航页由标题栏(Titlebar,包含菜单栏menu)、内容区(Navigation子组件)和工具栏(Toolbar)组成,其中导航页可以通过hideNavBar属性进行隐藏,导航页不存在页面栈中,导航页和子页,以及子页之间可以通过路由操作进行切换。

在API Version 9上,Navigation需要配合NavRouter组件实现页面路由。从API Version 10开始,更推荐使用NavPathStack实现页面路由。

设置页面显示模式

Navigation组件通过mode属性设置页面的显示模式。

  • 自适应模式

Navigation组件默认为自适应模式,此时mode属性为NavigationMode.Auto。自适应模式下,当页面宽度大于等于一定阈值( API version 9及以前:520vp,API version 10及以后:600vp )时,Navigation组件采用分栏模式,反之采用单栏模式。

Navigation() {// ...
}
.mode(NavigationMode.Auto)
  • 单页面模式

图1 单页面布局示意图

将mode属性设置为NavigationMode.Stack,Navigation组件即可设置为单页面显示模式。

Navigation() {// ...
}
.mode(NavigationMode.Stack)

  • 分栏模式

图2 分栏布局示意图

将mode属性设置为NavigationMode.Split,Navigation组件即可设置为分栏显示模式。

@Entry
@Component
struct NavigationExample {@State TooTmp: ToolbarItem = {'value': "func", 'icon': "./image/ic_public_highlights.svg", 'action': ()=> {}}@Provide('pageInfos') pageInfos: NavPathStack = new NavPathStack()private arr: number[] = [1, 2, 3];@BuilderPageMap(name: string) {if (name === "NavDestinationTitle1") {pageOneTmp()} else if (name === "NavDestinationTitle2") {pageTwoTmp()} else if (name === "NavDestinationTitle3") {pageThreeTmp()}}build() {Column() {Navigation(this.pageInfos) {TextInput({ placeholder: 'search...' }).width("90%").height(40).backgroundColor('#FFFFFF')List({ space: 12 }) {ForEach(this.arr, (item:number) => {ListItem() {Text("NavRouter" + item).width("100%").height(72).backgroundColor('#FFFFFF').borderRadius(24).fontSize(16).fontWeight(500).textAlign(TextAlign.Center).onClick(()=>{this.pageInfos.pushPath({ name: "NavDestinationTitle" + item})})}}, (item:number) => item.toString())}.width("90%").margin({ top: 12 })}.title("主标题").mode(NavigationMode.Split).navDestination(this.PageMap).menus([{value: "", icon: "./image/ic_public_search.svg", action: ()=> {}},{value: "", icon: "./image/ic_public_add.svg", action: ()=> {}},{value: "", icon: "./image/ic_public_add.svg", action: ()=> {}},{value: "", icon: "./image/ic_public_add.svg", action: ()=> {}},{value: "", icon: "./image/ic_public_add.svg", action: ()=> {}}]).toolbarConfiguration([this.TooTmp, this.TooTmp, this.TooTmp])}.height('100%').width('100%').backgroundColor('#F1F3F5')}
}// PageOne.ets
@Component
export struct pageOneTmp {@Consume('pageInfos') pageInfos: NavPathStack;build() {NavDestination() {Column() {Text("NavDestinationContent1")}.width('100%').height('100%')}.title("NavDestinationTitle1").onBackPressed(() => {const popDestinationInfo = this.pageInfos.pop() // 弹出路由栈栈顶元素console.log('pop' + '返回值' + JSON.stringify(popDestinationInfo))return true})}
}// PageTwo.ets
@Component
export struct pageTwoTmp {@Consume('pageInfos') pageInfos: NavPathStack;build() {NavDestination() {Column() {Text("NavDestinationContent2")}.width('100%').height('100%')}.title("NavDestinationTitle2").onBackPressed(() => {const popDestinationInfo = this.pageInfos.pop() // 弹出路由栈栈顶元素console.log('pop' + '返回值' + JSON.stringify(popDestinationInfo))return true})}
}// PageThree.ets
@Component
export struct pageThreeTmp {@Consume('pageInfos') pageInfos: NavPathStack;build() {NavDestination() {Column() {Text("NavDestinationContent3")}.width('100%').height('100%')}.title("NavDestinationTitle3").onBackPressed(() => {const popDestinationInfo = this.pageInfos.pop() // 弹出路由栈栈顶元素console.log('pop' + '返回值' + JSON.stringify(popDestinationInfo))return true})}
}

设置标题栏模式

标题栏在界面顶部,用于呈现界面名称和操作入口,Navigation组件通过titleMode属性设置标题栏模式。

  • Mini模式

普通型标题栏,用于一级页面不需要突出标题的场景。

图3 Mini模式标题栏

Navigation() {// ...
}
.titleMode(NavigationTitleMode.Mini)
  • Full模式

强调型标题栏,用于一级页面需要突出标题的场景。

图4 Full模式标题栏

Navigation() {// ...
}
.titleMode(NavigationTitleMode.Full)

设置菜单栏

菜单栏位于Navigation组件的右上角,开发者可以通过menus属性进行设置。menus支持Array<NavigationMenuItem>和CustomBuilder两种参数类型。使用Array<NavigationMenuItem>类型时,竖屏最多支持显示3个图标,横屏最多支持显示5个图标,多余的图标会被放入自动生成的更多图标。

图5 设置了3个图标的菜单栏

let TooTmp: NavigationMenuItem = {'value': "", 'icon': "./image/ic_public_highlights.svg", 'action': ()=> {}}
Navigation() {// ...
}
.menus([TooTmp,TooTmp,TooTmp])

图片也可以引用resources中的资源。

let TooTmp: NavigationMenuItem = {'value': "", 'icon': "resources/base/media/ic_public_highlights.svg", 'action': ()=> {}}
Navigation() {// ...
}
.menus([TooTmp,TooTmp,TooTmp])

图6 设置了4个图标的菜单栏

let TooTmp: NavigationMenuItem = {'value': "", 'icon': "./image/ic_public_highlights.svg", 'action': ()=> {}}
Navigation() {// ...
}
.menus([TooTmp,TooTmp,TooTmp,TooTmp])

设置工具栏

工具栏位于Navigation组件的底部,开发者可以通过toolbarConfiguration属性进行设置。

图7 工具栏

let TooTmp: ToolbarItem = {'value': "func", 'icon': "./image/ic_public_highlights.svg", 'action': ()=> {}}
let TooBar: ToolbarItem[] = [TooTmp,TooTmp,TooTmp]
Navigation() {// ...
}
.toolbarConfiguration(TooBar)

路由操作

Navigation路由相关的操作都是基于页面栈NavPathStack提供的方法进行,每个Navigation都需要创建并传入一个NavPathStack对象,用于管理页面。主要涉及页面跳转、页面返回、页面替换、页面删除、参数获取、路由拦截等功能。

从API version 12开始,页面栈允许被继承。开发者可以在派生类中自定义属性和方法,也可以重写父类的方法。派生类对象可以替代基类NavPathStack对象使用。

@Entry
@Component
struct Index {// 创建一个页面栈对象并传入NavigationpageStack: NavPathStack = new NavPathStack()build() {Navigation(this.pageStack) {}.title('Main')}
}

页面跳转

NavPathStack通过Push相关的接口去实现页面跳转的功能,主要分为以下三类:

  1. 普通跳转,通过页面的name去跳转,并可以携带param。
this.pageStack.pushPath({ name: "PageOne", param: "PageOne Param" })
this.pageStack.pushPathByName("PageOne", "PageOne Param")
  1. 带返回回调的跳转,跳转时添加onPop回调,能在页面出栈时获取返回信息,并进行处理。
this.pageStack.pushPathByName('PageOne', "PageOne Param", (popInfo) => {console.log('Pop page name is: ' + popInfo.info.name + ', result: ' + JSON.stringify(popInfo.result))
});
  1. 带错误码的跳转,跳转结束会触发异步回调,返回错误码信息。
this.pageStack.pushDestinationByName('PageOne', "PageOne Param").catch((error: BusinessError) => {console.error(`Push destination failed, error code = ${error.code}, error.message = ${error.message}.`);}).then(() => {console.info('Push destination succeed.');});

页面返回

NavPathStack通过Pop相关接口去实现页面返回功能。

// 返回到上一页
this.pageStack.pop()
// 返回到上一个PageOne页面
this.pageStack.popToName("PageOne")
// 返回到索引为1的页面
this.pageStack.popToIndex(1)
// 返回到根首页(清除栈中所有页面)
this.pageStack.clear()

页面替换

NavPathStack通过Replace相关接口去实现页面替换功能。

// 将栈顶页面替换为PageOne
this.pageStack.replacePath({ name: "PageOne", param: "PageOne Param" })
this.pageStack.replacePathByName("PageOne", "PageOne Param")

页面删除

NavPathStack通过Remove相关接口去实现删除页面栈中特定页面的功能。

// 删除栈中name为PageOne的所有页面
this.pageStack.removeByName("PageOne")
// 删除指定索引的页面
this.pageStack.removeByIndexes([1,3,5])

参数获取

NavPathStack通过Get相关接口去获取页面的一些参数。

// 获取栈中所有页面name集合
this.pageStack.getAllPathName()
// 获取索引为1的页面参数
this.pageStack.getParamByIndex(1)
// 获取PageOne页面的参数
this.pageStack.getParamByName("PageOne")
// 获取PageOne页面的索引集合
this.pageStack.getIndexByName("PageOne")

路由拦截

NavPathStack提供了setInterception方法,用于设置Navigation页面跳转拦截回调。该方法需要传入一个NavigationInterception对象,该对象包含三个回调函数:

名称

描述

willShow

页面跳转前回调,允许操作栈,在当前跳转生效。

didShow

页面跳转后回调,在该回调中操作栈会在下一次跳转生效。

modeChange

Navigation单双栏显示状态发生变更时触发该回调。

说明

无论是哪个回调,在进入回调时页面栈都已经发生了变化。

开发者可以在willShow回调中通过修改路由栈来实现路由拦截重定向的能力。

this.pageStack.setInterception({willShow: (from: NavDestinationContext | "navBar", to: NavDestinationContext | "navBar",operation: NavigationOperation, animated: boolean) => {if (typeof to === "string") {console.log("target page is navigation home page.");return;}// 将跳转到PageTwo的路由重定向到PageOnelet target: NavDestinationContext = to as NavDestinationContext;if (target.pathInfo.name === 'PageTwo') {target.pathStack.pop();target.pathStack.pushPathByName('PageOne', null);}}
})

子页面

NavDestination是Navigation子页面的根容器,用于承载子页面的一些特殊属性以及生命周期等。NavDestination可以设置独立的标题栏和菜单栏等属性,使用方法与Navigation相同。NavDestination也可以通过mode属性设置不同的显示类型,用于满足不同页面的诉求。

页面显示类型

  • 标准类型

NavDestination组件默认为标准类型,此时mode属性为NavDestinationMode.STANDARD。标准类型的NavDestination的生命周期跟随其在NavPathStack页面栈中的位置变化而改变。

  • 弹窗类型

NavDestination设置mode为NavDestinationMode.DIALOG弹窗类型,此时整个NavDestination默认透明显示。弹窗类型的NavDestination显示和消失时不会影响下层标准类型的NavDestination的显示和生命周期,两者可以同时显示。

// Dialog NavDestination
@Entry
@Component
struct Index {@Provide('NavPathStack') pageStack: NavPathStack = new NavPathStack()@BuilderPagesMap(name: string) {if (name == 'DialogPage') {DialogPage()}}build() {Navigation(this.pageStack) {Button('Push DialogPage').margin(20).width('80%').onClick(() => {this.pageStack.pushPathByName('DialogPage', '');})}.mode(NavigationMode.Stack).title('Main').navDestination(this.PagesMap)}
}@Component
export struct DialogPage {
@Consume('NavPathStack') pageStack: NavPathStack;build() {NavDestination() {Stack({ alignContent: Alignment.Center }) {Column() {Text("Dialog NavDestination").fontSize(20).margin({ bottom: 100 })Button("Close").onClick(() => {this.pageStack.pop()}).width('30%')}.justifyContent(FlexAlign.Center).backgroundColor(Color.White).borderRadius(10).height('30%').width('80%')}.height("100%").width('100%')}.backgroundColor('rgba(0,0,0,0.5)').hideTitleBar(true).mode(NavDestinationMode.DIALOG)
}
}

页面生命周期

Navigation作为路由容器,其生命周期承载在NavDestination组件上,以组件事件的形式开放。

其生命周期大致可分为三类,自定义组件生命周期、通用组件生命周期和自有生命周期。其中,aboutToAppearaboutToDisappear是自定义组件的生命周期(NavDestination外层包含的自定义组件),OnAppearOnDisappear是组件的通用生命周期。剩下的六个生命周期为NavDestination独有。

生命周期时序如下图所示:

  • aboutToAppear:在创建自定义组件后,执行其build()函数之前执行(NavDestination创建之前),允许在该方法中改变状态变量,更改将在后续执行build()函数中生效。
  • onWillAppear:NavDestination创建后,挂载到组件树之前执行,在该方法中更改状态变量会在当前帧显示生效。
  • onAppear:通用生命周期事件,NavDestination组件挂载到组件树时执行。
  • onWillShow:NavDestination组件布局显示之前执行,此时页面不可见(应用切换到前台不会触发)。
  • onShown:NavDestination组件布局显示之后执行,此时页面已完成布局。
  • onWillHide:NavDestination组件触发隐藏之前执行(应用切换到后台不会触发)。
  • onHidden:NavDestination组件触发隐藏后执行(非栈顶页面push进栈,栈顶页面pop出栈或应用切换到后台)。
  • onWillDisappear:NavDestination组件即将销毁之前执行,如果有转场动画,会在动画前触发(栈顶页面pop出栈)。
  • onDisappear:通用生命周期事件,NavDestination组件从组件树上卸载销毁时执行。
  • aboutToDisappear:自定义组件析构销毁之前执行,不允许在该方法中改变状态变量。

页面监听和查询

为了方便组件跟页面解耦,在NavDestination子页面内部的自定义组件可以通过全局方法监听或查询到页面的一些状态信息。

  • 页面信息查询

自定义组件提供queryNavDestinationInfo方法,可以在NavDestination内部查询到当前所属页面的信息,返回值为NavDestinationInfo,若查询不到则返回undefined。

import { uiObserver } from '@kit.ArkUI';// NavDestination内的自定义组件
@Component
struct MyComponent {navDesInfo: uiObserver.NavDestinationInfo | undefinedaboutToAppear(): void {this.navDesInfo = this.queryNavDestinationInfo();}build() {Column() {Text("所属页面Name: " + this.navDesInfo?.name)}.width('100%').height('100%')}
}
  • 页面状态监听

通过observer.on('navDestinationUpdate')提供的注册接口可以注册NavDestination生命周期变化的监听,使用方式如下:

uiObserver.on('navDestinationUpdate', (info) => {console.info('NavDestination state update', JSON.stringify(info));
});

也可以注册页面切换的状态回调,能在页面发生路由切换的时候拿到对应的页面信息NavDestinationSwitchInfo,并且提供了UIAbilityContext和UIContext不同范围的监听:

// 在UIAbility中使用
import { UIContext, uiObserver } from '@kit.ArkUI';// callBackFunc 是开发者定义的监听回调函数
function callBackFunc(info: uiObserver.NavDestinationSwitchInfo) {}
uiObserver.on('navDestinationSwitch', this.context, callBackFunc);// 可以通过窗口的getUIContext()方法获取对应的UIContent
uiContext: UIContext | null = null;
uiObserver.on('navDestinationSwitch', this.uiContext, callBackFunc);

页面转场

Navigation默认提供了页面切换的转场动画,通过页面栈操作时,会触发不同的转场效果(Dialog类型的页面默认无转场动画),Navigation也提供了关闭系统转场、自定义转场以及共享元素转场的能力。

三、案例页面展示

四、案例目录文件结构

五、项目资源初始化

color.json

{"color": [{"name": "start_window_background","value": "#FFFFFF"},{"name": "page_background","value": "#F1F3F5"},{"name": "item_background","value": "#FFFFFF"},{"name": "blue_background","value": "#007DFF"},{"name": "title_font","value": "#182431"},{"name": "font_background","value": "#FAFAFA"},{"name": "notice_font","value": "#FF7500"}]
}

float.json

{"float": [{"name": "btn_height","value": "40vp"},{"name": "value_height","value": "28vp"},{"name": "item_padding","value": "12vp"},{"name": "subtitle_margin_top","value": "2vp"},{"name": "blue_margin_top","value": "4vp"},{"name": "title_margin_top","value": "6vp"},{"name": "desc_margin_top","value": "8vp"},{"name": "item_margin_top","value": "18vp"},{"name": "desc_line_height","value": "20fp"},{"name": "subtitle_line_height","value": "16fp"},{"name": "name_font_size","value": "16fp"},{"name": "title_font_size","value": "14fp"},{"name": "subtitle_font_size","value": "12fp"},{"name": "large_font_size","value": "24fp"},{"name": "item_border_radius","value": "24vp"},{"name": "notice_border_radius","value": "12vp"},{"name": "value_border_radius","value": "4vp"},{"name": "label_opacity","value": "0.6"}]
}

string.json(英文)

{"string": [{"name": "module_desc","value": "module description"},{"name": "EntryAbility_desc","value": "description"},{"name": "EntryAbility_label","value": "label"},{"name": "pixel_introduce","value": "Pixel Introduce"},{"name": "pixel_conversion","value": "Pixel Conversion"},{"name": "HarmonyHeiTi","value": "HarmonyHeiTi"},{"name": "HarmonyHeiTi_Medium","value": "HarmonyHeiTi-Medium"},{"name": "font_desc","value": "This is a text for %dfp"},{"name": "notice","value": "For the conversion between lpx and px, set designWidth based on the site requirements."},{"name": "px_unit","value": "Physical pixel unit of the screen."},{"name": "vp_unit","value": "Screen density-related pixels, which are converted to screen physical pixels based on the screen pixel density."},{"name": "lpx_unit","value": "Logical pixel unit of the window. The unit of lpx is the ratio of the actual screen width to the logical width (configured by designWidth)."},{"name": "fp_unit","value": "Font pixel, similar to vp, varies with the system font size setting."},{"name": "vp_desc","value": "On a device with a pixel density of 160 dpi, 1vp = 1px, corresponding physical screen pixels = (Screen pixel density/160) px."},{"name": "fp_desc","value": "By default, the value is the same as that of vp, that is, 1vp=1fp. If the system font is manually adjusted, scale indicates the scaling ratio. The font size after setting (unit: fp) equals the font size before setting x scale."}]
}

string.json(中文)

{"string": [{"name": "module_desc","value": "模块描述"},{"name": "EntryAbility_desc","value": "description"},{"name": "EntryAbility_label","value": "像素转换"},{"name": "pixel_introduce","value": "像素介绍"},{"name": "pixel_conversion","value": "像素转换"},{"name": "HarmonyHeiTi","value": "HarmonyHeiTi"},{"name": "HarmonyHeiTi_Medium","value": "HarmonyHeiTi-Medium"},{"name": "font_desc","value": "这是一段为%dfp的文字"},{"name": "notice","value": "lpx与px之间的转换,需要根据实际情况设置designWidth"},{"name": "px_unit","value": "屏幕物理像素单位。"},{"name": "vp_unit","value": "屏幕密度相关像素,根据屏幕像素密度转换为屏幕物理像素。"},{"name": "lpx_unit","value": "视窗逻辑像素单位,lpx单位为实际屏幕宽度与逻辑宽度(通过designWidth配置)的比值。"},{"name": "fp_unit","value": "字体像素,与vp类似,随系统字体大小设置变化。"},{"name": "vp_desc","value": "像素密度为160dpi的设备上1vp=1px,1vp对应的物理屏幕像素=(屏幕像素密度/160)px "},{"name": "fp_desc","value": "默认情况下与vp相同,即1vp=1fp,如果用户手动调整了系统字体,scale为缩放比例,设置后的字体大小(单位fp) = 设置前的字体大小 * scale"}]
}

常量

// ets/common/constants/Constants.etsexport default class Constants {static readonly FULL_PERCENT: string = '100%'static readonly PIXEL_CONVERSION: string = '像素转换'static readonly PIXEL_INTRODUCTION: string = '像素介绍'static readonly INTRODUCTION_PAGE_URL: string = 'pages/IntroductionPage'static readonly CONVERSION_PAGE_URL: string = 'pages/ConversionPage'static readonly INDEX_PAGE_TAG: string = 'IndexPage push error 'static readonly TITLE_FONT_WEIGHT: number = 500static readonly LABEL_FONT_WEIGHT: number = 400static readonly COLUMN_SPACE: number = 24static readonly ITEM_PADDING: number = 12static readonly LARGE_FONT_SIZE: number = 24static readonly SMALL_FONT_SIZE: number = 14static readonly VP_SIZE: number = 60static readonly PIXEL_WIDTH: number = 200
}

Logger

// ets/common/utils/Logger.etsimport { hilog } from '@kit.PerformanceAnalysisKit'const LOGGER_PREFIX: string = 'Pixel Conversion'class Logger {private domain: numberprivate prefix: stringprivate format: string = '%{public}s, %{public}s'constructor(prefix: string = '', domain: number = 0xFF00) {this.prefix = prefixthis.domain = domain}debug(...args: string[]): void {hilog.debug(this.domain, this.prefix, this.format, args)}info(...args: string[]): void {hilog.info(this.domain, this.prefix, this.format, args)}warn(...args: string[]): void {hilog.warn(this.domain, this.prefix, this.format, args)}error(...args: string[]): void {hilog.error(this.domain, this.prefix, this.format, args)}
}export default new Logger(LOGGER_PREFIX)

六、页面

// ets/pages/Index.etsimport { router } from '@kit.ArkUI'
import Constants from '../common/constants/Constants'
import Logger from '../common/utils/Logger'@Entry
@Component
struct IndexPage {jumpPage(url: string) {router.pushUrl({ url }).catch((error: Error) => {Logger.error(Constants.INDEX_PAGE_TAG, JSON.stringify(error));});}build() {Column({ space: Constants.COLUMN_SPACE }) {Button($r('app.string.pixel_introduce')).height($r('app.float.btn_height')).width(Constants.FULL_PERCENT).backgroundColor($r('app.color.blue_background')).onClick((): void => this.jumpPage(Constants.INTRODUCTION_PAGE_URL))Button($r('app.string.pixel_conversion')).height($r('app.float.btn_height')).width(Constants.FULL_PERCENT).backgroundColor($r('app.color.blue_background')).onClick((): void => this.jumpPage(Constants.CONVERSION_PAGE_URL))}.backgroundColor($r('app.color.page_background')).justifyContent(FlexAlign.Center).padding(Constants.COLUMN_SPACE).width(Constants.FULL_PERCENT).height(Constants.FULL_PERCENT)}
}

七、像素介绍页面

// ets/viewmodel/IntroductionItem.etsexport default class IntroductionItem {name: stringtitle: Resource | stringsubTitle: Resource | stringvalue: stringsmallFontSize: numberlargeFontSize: numberconstructor(name?: string, title?: Resource | string, subTitle?: Resource | string, value?: string,smallFontSize?: number, largeFontSize?: number) {this.name = name ? name : ''this.title = title ? title : ''this.subTitle = subTitle ? subTitle : ''this.value = value ? value : ''this.smallFontSize = smallFontSize ? smallFontSize : 0this.largeFontSize = largeFontSize ? largeFontSize : 0}
}
// ets/viewmodel/InstroductionViewModel.etsimport Constants from '../common/constants/Constants'
import IntroductionItem from './IntroductionItem'class IntroductionViewModel {getIntroductionList() {let introductionItems = INTRODUCE_LISTreturn introductionItems;}
}const INTRODUCE_LIST: IntroductionItem[] = [new IntroductionItem('px', $r('app.string.px_unit'), '', Constants.PIXEL_WIDTH + 'px', 0, 0),new IntroductionItem('vp', $r('app.string.vp_unit'), $r('app.string.vp_desc'), Constants.PIXEL_WIDTH + 'vp', 0, 0),new IntroductionItem('lpx', $r('app.string.lpx_unit'), '', Constants.PIXEL_WIDTH + 'lpx', 0, 0),new IntroductionItem('fp', $r('app.string.fp_unit'), $r('app.string.fp_desc'), '', Constants.SMALL_FONT_SIZE,Constants.LARGE_FONT_SIZE)
]let introductionViewModel = new IntroductionViewModel()export default introductionViewModel as IntroductionViewModel
// ets/views/IntroductionComponent.etsimport Constants from '../common/constants/Constants'
import IntroductionItem from '../viewmodel/IntroductionItem'@Extend(Text) function titleTextStyle() {.fontColor($r('app.color.title_font')).fontFamily($r('app.string.HarmonyHeiTi_Medium')).fontWeight(Constants.TITLE_FONT_WEIGHT)
}@Component
export default struct IntroduceItemComponent {model: IntroductionItem = new IntroductionItem()build() {Column() {Text(this.model.name).titleTextStyle().fontSize($r('app.float.name_font_size'))Text(this.model.title).titleTextStyle().fontSize($r('app.float.title_font_size')).fontFamily($r('app.string.HarmonyHeiTi')).lineHeight($r('app.float.desc_line_height')).margin({ top: $r('app.float.desc_margin_top') }).fontWeight(Constants.LABEL_FONT_WEIGHT)if (this.model.subTitle) {Text(this.model.subTitle).titleTextStyle().opacity($r('app.float.label_opacity')).lineHeight($r('app.float.subtitle_line_height')).fontSize($r('app.float.subtitle_font_size')).fontFamily($r('app.string.HarmonyHeiTi')).margin({ top: $r('app.float.subtitle_margin_top') }).fontWeight(Constants.LABEL_FONT_WEIGHT)}if (this.model.value.length > 0) {Text(this.model.value).titleTextStyle().fontColor($r('app.color.item_background')).fontSize($r('app.float.name_font_size')).textAlign(TextAlign.Center).backgroundColor($r('app.color.blue_background')).height($r('app.float.value_height')).width(this.model.value).borderRadius($r('app.float.value_border_radius')).margin({ top: $r('app.float.item_padding') })} else {Column() {Text($r('app.string.font_desc', this.model.smallFontSize)).titleTextStyle().fontSize(this.model.smallFontSize)Text($r('app.string.font_desc', this.model.largeFontSize)).titleTextStyle().fontSize(this.model.largeFontSize).margin({ top: $r('app.float.title_margin_top') })}.alignItems(HorizontalAlign.Start).backgroundColor($r('app.color.font_background')).width(Constants.FULL_PERCENT).borderRadius($r('app.float.notice_border_radius')).padding($r('app.float.item_padding')).margin({ top: $r('app.float.item_padding') })}}.alignItems(HorizontalAlign.Start).width(Constants.FULL_PERCENT).padding($r('app.float.item_padding')).borderRadius($r('app.float.item_border_radius')).backgroundColor($r('app.color.item_background'))}
}
// ets/pages/IntroductionPage.etsimport Constants from '../common/constants/Constants'
import IntroductionItem from '../viewmodel/IntroductionItem'
import IntroduceItemComponent from '../views/IntroductionItemComponent'
import IntroductionViewModel from '../viewmodel/IntroductionViewModel'@Entry
@Component
struct IntroductionPage {build() {Column() {Navigation() {List({ space: Constants.ITEM_PADDING }) {ForEach(IntroductionViewModel.getIntroductionList(), (item: IntroductionItem) => {ListItem() {IntroduceItemComponent({ model: item })}.padding({left: $r('app.float.item_padding'),right: $r('app.float.item_padding')})})}.width(Constants.FULL_PERCENT).height(Constants.FULL_PERCENT)}.titleMode(NavigationTitleMode.Mini).title(Constants.PIXEL_INTRODUCTION)}.backgroundColor($r('app.color.page_background')).width(Constants.FULL_PERCENT).height(Constants.FULL_PERCENT)}
}

八、像素转换页面

// ets/viewmodel/ConversionItem.etsexport default class ConversionItem {title: stringsubTitle: stringvalue: number = 0conversionTitle: stringconversionSubTitle: stringconversionValue: numbernotice?: Resource | stringconstructor(title?: string, subTitle?: string, value?: number, conversionTitle?: string, conversionSubTitle?: string,conversionValue?: number, notice?: Resource | string) {this.title = title ? title : ''this.subTitle = subTitle ? subTitle : ''this.value = value ? value : 0this.conversionTitle = conversionTitle ? conversionTitle : ''this.conversionSubTitle = conversionSubTitle ? conversionSubTitle : ''this.conversionValue = conversionValue ? conversionValue : 0this.notice = notice ? notice : ''}
}
// ets/viewmodel/ConversionViewModel.etsimport Constants from '../common/constants/Constants'
import ConversionItem from './ConversionItem'class ConversionViewModel {getConversionList() {let conversionItems = CONVERSION_LIST;return conversionItems;}
}export const CONVERSION_LIST: ConversionItem[] = [new ConversionItem('vp > px', `vp2px(${Constants.VP_SIZE})`, vp2px(Constants.VP_SIZE), 'px > vp',`px2vp(${Constants.VP_SIZE})`, px2vp(Constants.VP_SIZE)),new ConversionItem('fp > px',`fp2px(${Constants.VP_SIZE})`,fp2px(Constants.VP_SIZE),'px > fp',`px2fp(${Constants.VP_SIZE})`,px2fp(Constants.VP_SIZE)),new ConversionItem('lpx > px',`lpx2px(${Constants.VP_SIZE})`,lpx2px(Constants.VP_SIZE),'px > lpx',`px2lpx(${Constants.VP_SIZE})`,px2lpx(Constants.VP_SIZE),$r('app.string.notice'))
]let conversionViewModel = new ConversionViewModel()export default conversionViewModel as ConversionViewModel
// ets/views/ConversionItemComponent.etsimport Constants from '../common/constants/Constants'
import ConversionItem from '../viewmodel/ConversionItem'@Extend(Text) function descTextStyle() {.fontColor($r('app.color.title_font')).fontSize($r('app.float.title_font_size')).fontFamily($r('app.string.HarmonyHeiTi')).lineHeight($r('app.float.desc_line_height')).fontWeight(Constants.LABEL_FONT_WEIGHT).margin({ top: $r('app.float.desc_margin_top') })
}@Extend(Text) function titleTextStyle() {.fontColor($r('app.color.title_font')).fontSize($r('app.float.name_font_size')).fontFamily($r('app.string.HarmonyHeiTi_Medium')).fontWeight(Constants.TITLE_FONT_WEIGHT)
}@Styles function blueStyle() {.backgroundColor($r('app.color.blue_background')).height($r('app.float.value_height')).borderRadius($r('app.float.value_border_radius')).margin({ top: $r('app.float.blue_margin_top') })
}@Component
export default struct ConversionItemComponent {model: ConversionItem = new ConversionItem()build() {Column() {Text(this.model.title).titleTextStyle().margin({ top: $r('app.float.title_margin_top') })Text(this.model.subTitle).descTextStyle().opacity($r('app.float.label_opacity'))Row().blueStyle().width(this.model.value)Text(this.model.conversionTitle).titleTextStyle().margin({ top: $r('app.float.item_margin_top') })Text(this.model.conversionSubTitle).descTextStyle().opacity($r('app.float.label_opacity'))Row().blueStyle().width(this.model.conversionValue)if (this.model.notice) {Text(this.model.notice).descTextStyle().fontColor($r('app.color.notice_font'))}}.alignItems(HorizontalAlign.Start).width(Constants.FULL_PERCENT).padding($r('app.float.item_padding')).borderRadius($r('app.float.item_border_radius')).backgroundColor($r('app.color.item_background'))}
}
// ets/pages/ConversionPage.etsimport Constants from '../common/constants/Constants'
import ConversionItemComponent from '../views/ConversionItemComponent'
import ConversionItem from '../viewmodel/ConversionItem'
import ConversionViewModel from '../viewmodel/ConversionViewModel'@Entry
@Component
struct ConversionPage {build() {Column() {Navigation() {List({ space: Constants.ITEM_PADDING }) {ForEach(ConversionViewModel.getConversionList(), (item: ConversionItem) => {ListItem() {ConversionItemComponent({ model: item })}.padding({left: $r('app.float.item_padding'),right: $r('app.float.item_padding')})})}.width(Constants.FULL_PERCENT).height(Constants.FULL_PERCENT)}.titleMode(NavigationTitleMode.Mini).title(Constants.PIXEL_CONVERSION)}.backgroundColor($r('app.color.page_background')).width(Constants.FULL_PERCENT).height(Constants.FULL_PERCENT)}
}


http://www.hkcw.cn/article/alvyAGPDwt.shtml

相关文章

结构型设计模式之桥接模式

文章目录 1. 桥接模式概述2. 模式结构3. 桥接模式的优缺点优点缺点 4. 桥接模式的应用场景5. C#代码示例5.1 简单示例 - 形状与颜色5.2 更复杂的示例 - 跨平台消息发送系统 6. 桥接模式与其他模式的比较7. 真实世界中的桥接模式应用7.1 数据库驱动7.2 UI框架中的渲染机制 8. 桥…

RAG系统中如何检测幻觉?

虽然我们的 RAG 系统通过将答案基于真实的医学证据来减少幻觉,但我们发现了一个关键的差距:即使有引用,系统仍然可能产生不可靠的输出。 想想看:仅仅因为一个系统可以引用来源,并不意味着它正确地使用了这些来源。 模型可能会: 从检索到的文档中提取不相关的信息不适当…

world quant教程学习

Understanding Corporate Fundamental Data &#x1f50d; 了解企业基本面数据 Lets explore fundamental data&#x1f60a; Fundamentals capture the underlying business, financial and operational health of a company, usually reported every quarter. This data is t…

详解鸿蒙仓颉开发语言中的计时器

今天又到了大家喜闻乐见的科普环节&#xff0c;也可以说是踩坑环节&#xff0c;哈哈哈。今天聊一聊仓颉开发语言中的计时器&#xff0c;这部分可老有意思了。 为什么这么说呢&#xff0c;因为关于仓颉的计时器你几乎搜不到任何的文档&#xff0c;也没有相关的代码提示&#xf…

70多套创业商业融资计划书PPT模板分享

70多套创业商业融资计划书PPT模板分享&#xff0c;商业计划书、融资计划书为主的欧美风格PPT模板。 70多套创业商业融资计划书PPT模板分享&#xff1a;创业商业融资计划书PPT模板https://pan.quark.cn/s/e09456cd487b

基于 StarRocks + Iceberg,TRM Labs 构建 PB 级数据分析平台实践

作者&#xff1a; Vijay Shekhawat&#xff1a;TRM Labs 数据平台团队核心成员&#xff0c;精通实时流处理、数据湖仓架构及构建安全、高吞吐的数据分析管道&#xff0c;在推动 PB 级数据处理能力方面发挥了关键作用。 Andrew Fisher&#xff1a;TRM Labs 资深软件工程师&…

Python----目标检测(使用YOLO 模型进行线程安全推理和流媒体源)

一、线程安全推理 在多线程环境中运行YOLO 模型需要仔细考虑&#xff0c;以确保线程安全。Pythons threading 模块允许您同时运行多个线程&#xff0c;但在这些线程中使用YOLO 模型时&#xff0c;需要注意一些重要的安全问题。本页将指导您创建线程安全的YOLO 模型推理。 1.1、…

机器学习知识图谱——朴素贝叶斯算法

目录 一、图解朴素贝叶斯算法知识图谱 二、基本概念 三、核心思想 四、为什么叫“朴素”? 五、算法流程图 六、常见模型类型 七、优点 与 缺点 八、实战代码 (以文本分类为例) 九、应用举例 机器学习知识图谱——朴素贝叶斯算法 一、图解朴素贝叶斯算法知识图谱 该…

ollama+open-webui,本地部署自己的大模型

目录 一、效果预览 二、部署ollama 1.ollama说明 2.安装流程 2.1 windows系统 2.1.1下载安装包 2.1.2验证安装结果 2.1.3设置模型文件保存地址 2.1.4拉取大模型镜像 2.2linux系统 2.2.1下载并安装ollama 2.2.2设置环境变量 2.2.3拉取模型文件 三、部署open-webui…

大模型赋能:2D 写实数字人开启实时交互新时代

在数字化浪潮席卷全球的当下&#xff0c;人工智能技术不断突破创新&#xff0c;其中大模型驱动的 2D 写实数字人正成为实时交互领域的一颗新星&#xff0c;引领着行业变革&#xff0c;为人们带来前所未有的交互体验。 一、2D 写实数字人概述 2D 写实数字人是通过计算机图形学…

效率工具- git rebase 全解

一、前言 对于git rebase 一直不太了解,这几天想着提高下git提交质量,就发现了这个好用的指令,顺便记录一下,好加深记忆 贴出官方文档以便大家进一步学习 Git 二、rebase是作用 rebase 官方解释为变基,可以理解为移动你的分支根节点,维护一个更好的提交记录。rebase把你当前…

【开源】Python打造高效剪贴板历史管理器:实现跨平台生产力工具

&#x1f4cb;【开源】Python打造高效剪贴板历史管理器&#xff1a;实现跨平台生产力工具 &#x1f308; 个人主页&#xff1a;创客白泽 - CSDN博客 &#x1f525; 系列专栏&#xff1a;&#x1f40d;《Python开源项目实战》 &#x1f4a1; 热爱不止于代码&#xff0c;热情源自…

π0的微调——如何基于各种开源数据集、以及私有数据集微调openpi(含我司七月的微调实践及在机械臂上的部署)

前言 25年2.4日&#xff0c;几个月前推出π0的公司Physical Intelligence (π)宣布正式开源π0及π0-FAST&#xff0c;如之前所介绍的&#xff0c;他们对用超过 10,000 小时的机器人数据进行了预训练 该GitHub代码仓库「 π0及π0-FAST的GitHub地址&#xff1a;github.com/Ph…

开源模型应用落地-qwen模型小试-Qwen3-8B-融合VLLM、MCP与Agent(七)

一、前言 随着Qwen3的开源与技术升级,其在企业中的落地场景正加速拓展至多个垂直领域。依托Agent智能体能力 和MCP协议的工具调用接口 ,Qwen3可深度融入企业业务流程,为企业提供从需求解析到自动化开发的全链路支持。 本篇将介绍如何实现Qwen3-8B模型集成MCP实现智能体交互。…

【Git】GitHub 连接失败解决方案:Failed to connect to github.com port 443 after 21090 ms: Couldn’t connect to se

文章目录 一、使用 VPN 环境下的解决方案1. 检查当前代理设置2. 配置 Git 使用代理3. 验证代理设置是否生效4. 刷新 DNS 缓存5. 重新尝试 Git 操作 二、未使用 VPN 环境下的解决方案1. 取消 Git 配置的代理2. 验证代理设置已成功移除3. 重试 Git 操作 三、总结使用 VPN 的解决方…

Java 大视界 -- Java 大数据机器学习模型在元宇宙虚拟场景智能交互中的关键技术(239)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

Digital Reengineering and Localized Implementation of the Five-Dimensional Management Cycle System

A Paradigm Shift in Intelligent Hospital Governance(Preliminary draft of the first-line cooperation project) Abstract This study pioneers a transformative approach to healthcare management through the “Technology-Management-Value” (TMV) triad model, r…

Qwen3:重磅开源,重夺开源第一!(包含详细使用教程)

1.简介 Qwen3&#xff0c;这是 Qwen 系列大型语言模型的最新成员。我们的旗舰模型 Qwen3-235B-A22B 在代码、数学、通用能力等基准测试中&#xff0c;与 DeepSeek-R1、o1、o3-mini、Grok-3 和 Gemini-2.5-Pro 等顶级模型相比&#xff0c;表现出极具竞争力的结果。此外&#xf…

基于 Alpine 定制单功能用途(kiosk)电脑

前言 故事回到 7 年前, 在网上冲浪的时候发现了一篇介绍使用 Ubuntu 打造 kiosk 单功能用途电脑的文章, 挺好玩的, 就翻译了一下并比葫芦画瓢先后用了 CentOS 7, ArchLinux 进行了实现. 历史文章: 翻译 - 使用Ubutnu14.04和Chrome打造单功能用途电脑(大屏展示电脑) 使用CentOS…

《汇编语言》第13章 int指令——实验13 编写、应用中断例程

&#xff08;1&#xff09;编写并安装 int 7ch 中断例程&#xff0c;功能为显示一个用0结束的字符串&#xff0c;中断例程安装在0&#xff1a;200处。 参数&#xff1a;&#xff08;dh&#xff09;行号&#xff0c;&#xff08;dl&#xff09;列号&#xff0c;&#xff08;cl&a…