RN for OpenHarmony 小工具 App 实战:屏幕尺子实现

这一篇实现一个「屏幕尺子」小工具:在手机屏幕上绘制 cm / inch 两条刻度尺,并展示当前设备屏幕宽度(px)与"换算后的大致厘米数"。

需要先说明一个现实限制:

  • 屏幕尺子的精度取决于设备的 PPI(像素密度)与系统缩放
  • 代码里用的是近似换算(pxPerCm = 37.8),因此适合"粗略参考",不适合精密测量。

本文所有代码片段均来自仓库真实文件:

  • src/pages/Ruler.tsx
  • src/tools/index.ts
  • src/screens/ToolScreen.tsx

你要求"每一段代码后面跟着的文字解释多一些",本文会按"代码块 -> 紧跟解释"的方式组织。


01. 这个小工具的设计目标

屏幕尺子是典型的"看起来简单,细节很多"的小工具。目标不是做实验室级精度,而是:

  • 可用:能滑动、能看清刻度
  • 直观:整数刻度有数字,半刻度/四分之一刻度有不同长度
  • 自解释:明确告诉用户精度可能不准

同时,它非常适合作为 RN UI 练习:

  • 刻度生成(循环渲染)
  • ScrollView 横向滚动
  • px 与 cm/inch 的比例换算
  • 小而美的入场动画

02. 工具箱接入:id=54 映射到 Ruler

2.1 工具列表注册

文件:src/tools/index.ts

ts 复制代码
{ id: 54, name: '尺子', description: '屏幕尺子工具', icon: '📏', component: 'Ruler' },

解释:

  • component: 'Ruler' 是 ToolScreen 查表渲染的 key
  • 📏 图标在工具网格里容易识别
  • description 简洁明确:这是一个屏幕尺子工具

2.2 ToolScreen 映射

文件:src/screens/ToolScreen.tsx

tsx 复制代码
Ruler: Pages.Ruler,

解释:

  • 工具箱会根据配置中的字符串 RulercomponentMap 找到 Pages.Ruler
  • 这种"配置驱动"的结构意味着:文章和代码的对应关系非常稳定

03. 屏幕像素信息:Dimensions.get('window')

文件:src/pages/Ruler.tsx

tsx 复制代码
import { View, Text, StyleSheet, ScrollView, Dimensions, Animated } from 'react-native';

const { width: screenWidth } = Dimensions.get('window');

解释:

  • Dimensions.get('window') 返回当前窗口的尺寸信息(像素单位)
  • 这里我们只取 width,用于:
    • "屏幕宽度 px"展示
    • 估算"屏幕宽度约等于多少 cm"

为什么用 window 而不是 screen

  • window 更接近应用可用区域
  • screen 可能包含状态栏等不可用区域(不同平台行为有差异)

对于"尺子"这种工具,精度本来就不是严格保证,所以取 window 是更合理的工程选择。


04. 核心参数:cmCount 与 pxPerCm

tsx 复制代码
const cmCount = 20;
const pxPerCm = 37.8;

解释:

4.1 cmCount 的意义

cmCount=20 代表我们要画 0~20cm 的刻度。

  • 每 1cm 需要 10 个小刻度(0.1cm)
  • 因此后续会生成 cmCount * 10 + 1 个 tick

为什么要 +1?因为 0 也要画出来。

4.2 pxPerCm = 37.8 从哪来?

这个数字来自一个常见近似:

  • 96 dpi 的换算:1 inch = 96 px
  • 1 inch = 2.54 cm
  • 所以 1 cm ≈ 96 / 2.54 ≈ 37.8 px

它不是"设备真实 PPI",但作为"参考级工具"足够。

如果你想做得更准,必须知道设备真实 dpi / ppi。


05. 入场动画:fade + slide(并行动画)

tsx 复制代码
const fadeAnim = useRef(new Animated.Value(0)).current;
const slideAnim = useRef(new Animated.Value(-20)).current;

useEffect(() => {
  Animated.parallel([
    Animated.timing(fadeAnim, { toValue: 1, duration: 500, useNativeDriver: true }),
    Animated.spring(slideAnim, { toValue: 0, friction: 6, useNativeDriver: true }),
  ]).start();
}, []);

解释:

  • fadeAnim 控制 header 的透明度:0 -> 1
  • slideAnim 控制 header 的 Y 位移:-20 -> 0(从上往下滑入)
  • Animated.parallel 让两个动画同时执行

为什么这里用 timing + spring 的组合?

  • 淡入用 timing 就够了,稳定、可控
  • 位移用 spring 会更自然,有一点"落下来的惯性"

为什么只动画 header?

  • 主尺子是 ScrollView 内容,动画它会影响滚动体验
  • header 动一下就足够让页面"有启动感",而不会干扰主要功能

06. cm 刻度渲染:把 1cm 切成 10 个 tick

tsx 复制代码
{Array.from({ length: cmCount * 10 + 1 }).map((_, i) => {
  const isCm = i % 10 === 0;
  const isHalfCm = i % 5 === 0 && !isCm;
  return (
    <View key={i} style={styles.tickContainer}>
      <View style={[styles.tick, isCm && styles.tickCm, isHalfCm && styles.tickHalf]} />
      {isCm && <Text style={styles.tickLabel}>{i / 10}</Text>}
    </View>
  );
})}

解释(这是这页最核心的"刻度生成"):

6.1 为什么是 cmCount * 10 + 1

因为我们把 1cm 切成 10 个小刻度(每格 0.1cm)。

  • 20cm 就是 200 个小刻度
  • +1 是为了把 0cm 的刻度画出来

6.2 isCmisHalfCm 的判定思路

  • isCm:每 10 个 tick 一个整厘米
  • isHalfCm:每 5 个 tick 一个 0.5cm(但要排除整厘米)

这样你会得到三种高度:

  • 0.1cm:短线
  • 0.5cm:中线
  • 1cm:长线 + 数字

6.3 为什么数字只在整厘米显示?

如果每个小刻度都显示数字,UI 会极度拥挤。

整厘米显示数字是尺子类工具的通用设计:

  • 信息密度合理
  • 用户读数不会困惑

07. inch 刻度渲染:1 inch 切成 8 份(1/8)

tsx 复制代码
{Array.from({ length: 8 * 8 + 1 }).map((_, i) => {
  const isInch = i % 8 === 0;
  const isHalf = i % 4 === 0 && !isInch;
  const isQuarter = i % 2 === 0 && !isHalf && !isInch;
  return (
    <View key={i} style={[styles.tickContainer, { width: pxPerCm * 2.54 / 8 }]}>
      <View style={[styles.tick, isInch && styles.tickCm, isHalf && styles.tickHalf, isQuarter && styles.tickQuarter]} />
      {isInch && <Text style={styles.tickLabel}>{i / 8}</Text>}
    </View>
  );
})}

解释:

7.1 为什么 1 inch 切成 8 份?

常见尺子会使用 1/8 inch 或 1/16 inch 作为最小刻度。

这里选择 1/8 是一个折中:

  • 刻度足够细
  • 但不会多到难以辨认

7.2 tick 宽度为何是 pxPerCm * 2.54 / 8

  • 1 inch = 2.54 cm
  • 1/8 inch = 2.54/8 cm
  • 再乘以 pxPerCm 就得到每个 tick 的宽度(px)

这样 inch 刻度就会与 cm 刻度在同一套 px 换算体系下保持一致。

7.3 1/2 与 1/4 刻度高度区分

  • isInch:整英寸(最高)
  • isHalf:半英寸(次高)
  • isQuarter:四分之一英寸(中等)
  • 其他:八分之一(最短)

这也是典型的英制尺子视觉层次。


08. 尺子横向滚动:为什么用 ScrollView horizontal?

tsx 复制代码
<ScrollView horizontal style={styles.rulerContainer} showsHorizontalScrollIndicator={false}>
  <View style={styles.ruler}>
    {/* ticks */}
  </View>
</ScrollView>

解释:

  • 刻度尺天然是横向延伸的
  • horizontal 可以让用户像使用真实尺子一样拖动查看
  • showsHorizontalScrollIndicator={false} 隐藏滚动条,让视觉更像"尺子"而不是"列表"

这里有个设计细节:

  • 内层用 View 来承载 ticks,而不是直接在 ScrollView 里 map
  • 这样刻度背景(黄色/蓝色)可以作为一整条条带显示

09. 屏幕信息展示:px 与 cm 的换算

tsx 复制代码
<Text style={styles.infoValue}>{screenWidth.toFixed(0)} px</Text>
<Text style={styles.infoValue}>{(screenWidth / pxPerCm).toFixed(1)} cm</Text>

解释:

  • screenWidth 是 px
  • screenWidth / pxPerCm 得到"估算厘米"

为什么只保留 1 位小数?

  • 因为 pxPerCm 本身就是近似值
  • 显示过多小数会给用户一种"很精确"的错觉

这类工具更应该强调"参考",而不是制造误导。


10. 关键样式:刻度宽度与刻度高度

来自 src/pages/Ruler.tsx

tsx 复制代码
tickContainer: { width: 3.78, alignItems: 'center' },
tick: { width: 1, height: 10, backgroundColor: '#333' },
tickCm: { height: 30, width: 2 },
tickHalf: { height: 20 },
tickQuarter: { height: 15 },

解释:

10.1 为什么 tickContainer.width = 3.78

它对应"0.1cm"的宽度:

  • 1cm ≈ 37.8px
  • 0.1cm ≈ 3.78px

所以 cm 尺子每个小刻度正好占用 3.78px。

10.2 不同刻度高度的意义

如果所有刻度高度一样,用户会难以快速定位整数刻度。

高度分层会形成"视觉导航":

  • 找到长线 -> 读数字
  • 在长线之间用中线/短线估算

这就是尺子类 UI 的核心可读性来源。


11. 小结

这个「屏幕尺子」工具的实现重点在于:

  • 通过 pxPerCm 把"物理长度"映射到"屏幕像素"
  • cm / inch 两套刻度各自按常见规则分桶渲染
  • 用 ScrollView horizontal 提供自然的尺子滑动体验
  • 明确提示"精度因设备而异",避免误用

如果你后续想做得更精确,关键不在 UI,而在拿到真实 PPI 并计算 pxPerCm


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
No Silver Bullet2 小时前
HarmonyOS NEXT开发进阶(十九):如何在 DevEco Studio 中查看已安装应用的运行日志
华为·harmonyos
丝斯20112 小时前
AI学习笔记整理(50)——大模型中的Graph RAG
人工智能·笔记·学习
小+不通文墨3 小时前
“超声波测量声速”实验报告
经验分享·笔记·学习·学习方法
大雷神3 小时前
HarmonyOS智慧农业管理应用开发教程--高高种地
华为·harmonyos
lihao lihao4 小时前
平衡二叉树
笔记
JavaLearnerZGQ4 小时前
我的Redis笔记2【分布式缓存】
redis·笔记·缓存
代码游侠4 小时前
复习——ARM Cortex-A 裸机开发深度解析
arm开发·笔记·嵌入式硬件·学习·架构
吗喽1543451884 小时前
渗透高级第一次作业(笔记整理)
笔记·安全·网络安全
南村群童欺我老无力.4 小时前
Flutter 框架跨平台鸿蒙开发 - 开发双人对战五子棋游戏
flutter·游戏·华为·typescript·harmonyos