HarmonyOS 应用开发基础案例(十一):像素转换案例实战

23 阅读4分钟

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

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

一、案例页面展示

二、案例目录文件结构

三、项目资源初始化

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.ets

export 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 = 500

  static readonly LABEL_FONT_WEIGHT: number = 400

  static readonly COLUMN_SPACE: number = 24

  static readonly ITEM_PADDING: number = 12

  static readonly LARGE_FONT_SIZE: number = 24

  static readonly SMALL_FONT_SIZE: number = 14

  static readonly VP_SIZE: number = 60

  static readonly PIXEL_WIDTH: number = 200
}

Logger

// ets/common/utils/Logger.ets

import { hilog } from '@kit.PerformanceAnalysisKit'

const LOGGER_PREFIX: string = 'Pixel Conversion'

class Logger {
  private domain: number
  private prefix: string

  private format: string = '%{public}s, %{public}s'

  constructor(prefix: string = '', domain: number = 0xFF00) {
    this.prefix = prefix
    this.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.ets

import { 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.ets

export default class IntroductionItem {
  name: string
  title: Resource | string
  subTitle: Resource | string
  value: string
  smallFontSize: number
  largeFontSize: number

  constructor(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 : 0
    this.largeFontSize = largeFontSize ? largeFontSize : 0
  }
}
// ets/viewmodel/InstroductionViewModel.ets

import Constants from '../common/constants/Constants'
import IntroductionItem from './IntroductionItem'

class IntroductionViewModel {
  getIntroductionList() {
    let introductionItems = INTRODUCE_LIST
    return 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.ets

import 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.ets

import 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.ets

export default class ConversionItem {
  title: string
  subTitle: string
  value: number = 0
  conversionTitle: string
  conversionSubTitle: string
  conversionValue: number
  notice?: Resource | string

  constructor(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 : 0
    this.conversionTitle = conversionTitle ? conversionTitle : ''
    this.conversionSubTitle = conversionSubTitle ? conversionSubTitle : ''
    this.conversionValue = conversionValue ? conversionValue : 0
    this.notice = notice ? notice : ''
  }
}
// ets/viewmodel/ConversionViewModel.ets

import 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.ets

import 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.ets

import 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)
  }
}

✋ 需要参加鸿蒙认证的请点击 鸿蒙认证链接

****