
这一篇实现一个「屏幕尺子」小工具:在手机屏幕上绘制 cm / inch 两条刻度尺,并展示当前设备屏幕宽度(px)与"换算后的大致厘米数"。
需要先说明一个现实限制:
- 屏幕尺子的精度取决于设备的 PPI(像素密度)与系统缩放。
- 代码里用的是近似换算(
pxPerCm = 37.8),因此适合"粗略参考",不适合精密测量。
本文所有代码片段均来自仓库真实文件:
src/pages/Ruler.tsxsrc/tools/index.tssrc/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,
解释:
- 工具箱会根据配置中的字符串
Ruler从componentMap找到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 -> 1slideAnim控制 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 isCm 与 isHalfCm 的判定思路
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是 pxscreenWidth / 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