RN for OpenHarmony 小工具 App 实战:文字放大镜实现
本文实现了一个专业的文字放大镜工具,主要解决日常场景中文字展示的需求。工具支持自定义文字内容、6档字体大小快速切换、6种主题颜色一键更换、全屏沉浸式展示以及流畅的切换动画。技术实现上采用React Native开发,使用useState管理状态,Animated实现动画效果,包括入场淡入动画和字号切换弹性动画。工具通过配置驱动方式注册到系统,具有良好的可扩展性和维护性。核心功能包括:文字输入区、字

引言:为什么需要文字放大镜?
在日常生活中,我们经常会遇到需要向他人展示文字的场景:
- 🏥 医院排队:显示自己的号码给护士看
- 🚗 打车接人:举着手机显示接机牌
- 📢 会议演示:临时展示关键信息
- 👴 老年人阅读:放大文字方便阅读
传统的做法是打开备忘录,把字号调到最大。但这种方式有几个问题:
- 字号调整不够灵活
- 背景颜色单一,可能在某些光线下看不清
- 没有专门的全屏展示模式
本文将实现一个专业的「文字放大镜」工具,支持:
- ✅ 自定义文字内容
- ✅ 6 档字体大小快速切换
- ✅ 6 种主题颜色一键更换
- ✅ 全屏沉浸式展示
- ✅ 流畅的切换动画
本文所有代码均来自项目真实文件
src/pages/Magnifier.tsx
一、功能架构与技术选型
1.1 功能模块划分
整个放大镜工具可以分为两大区域:
| 区域 | 功能 | 占比 |
|---|---|---|
| 展示区 | 全屏显示放大后的文字 | ~60% |
| 控制区 | 输入文字、调整大小、切换颜色 | ~40% |
这种布局设计的考量:
- 展示区占据主要空间,确保文字足够大、足够醒目
- 控制区收纳在底部,不干扰主要展示,但随时可调整
1.2 技术要点预览
| 技术点 | 用途 | React Native API |
|---|---|---|
| 状态管理 | 文字、字号、颜色 | useState |
| 入场动画 | 页面淡入效果 | Animated.timing |
| 交互动画 | 字号切换弹性效果 | Animated.sequence + spring |
| 滚动容器 | 长文字滚动查看 | ScrollView |
| 触摸反馈 | 按钮点击响应 | TouchableOpacity |
二、工具注册与路由配置
在开始核心代码之前,先看看这个工具是如何被注册到工具箱系统中的。
2.1 工具列表注册
文件:src/tools/index.ts
{ id: 55, name: '放大镜', description: '文字放大显示', icon: '🔍', component: 'Magnifier' },
代码解析:
这行配置定义了放大镜工具的元信息:
id: 55:工具的唯一标识符,用于路由跳转和数据关联name: '放大镜':在工具列表中显示的名称,简洁明了description: '文字放大显示':工具的功能描述,帮助用户理解用途icon: '🔍':使用放大镜 emoji 作为图标,直观表达功能component: 'Magnifier':对应的组件名称,用于动态加载
💡 设计思考:为什么用 emoji 而不是图片图标?
- 无需额外资源文件,减小包体积
- 跨平台一致性好
- 修改方便,不需要设计师介入
2.2 路由映射配置
文件:src/screens/ToolScreen.tsx
Magnifier: Pages.Magnifier,
代码解析:
这行代码建立了字符串 'Magnifier' 到实际组件 Pages.Magnifier 的映射关系。
工具箱的路由机制是这样工作的:
- 用户点击工具卡片,携带
component: 'Magnifier'跳转 ToolScreen接收到参数,在componentMap中查找- 找到
Pages.Magnifier组件并渲染
这种「配置驱动」的架构有几个好处:
- 解耦:工具配置与组件实现分离
- 可扩展:新增工具只需添加配置,无需修改路由逻辑
- 可维护:所有工具的入口一目了然
三、组件结构与状态设计
3.1 导入依赖
文件:src/pages/Magnifier.tsx
import React, { useState, useRef, useEffect } from 'react';
import { View, Text, TextInput, StyleSheet, ScrollView, TouchableOpacity, Animated } from 'react-native';
依赖分析:
| 导入项 | 来源 | 用途 |
|---|---|---|
useState |
React | 管理文字、字号、颜色等状态 |
useRef |
React | 持有动画值的引用,避免重新创建 |
useEffect |
React | 处理组件挂载时的入场动画 |
View |
RN | 布局容器 |
Text |
RN | 文字显示 |
TextInput |
RN | 用户输入文字 |
StyleSheet |
RN | 样式定义 |
ScrollView |
RN | 长文字滚动 |
TouchableOpacity |
RN | 可点击按钮,带透明度反馈 |
Animated |
RN | 动画系统 |
🎯 最佳实践:只导入需要的组件,避免导入整个模块,有助于 Tree Shaking 优化包体积。
3.2 状态定义
export const Magnifier: React.FC = () => {
const [text, setText] = useState('在此输入文字');
const [fontSize, setFontSize] = useState(48);
const [bgColor, setBgColor] = useState('#0f0f23');
const [textColor, setTextColor] = useState('#fff');
状态设计详解:
📝 text - 显示的文字内容
const [text, setText] = useState('在此输入文字');
- 初始值:
'在此输入文字'作为占位提示 - 为什么不用空字符串? 空字符串会让展示区看起来"空荡荡"的,用户可能不知道这里会显示什么
- 交互设计:用户点击输入框时,可以选择全选删除或追加输入
🔤 fontSize - 字体大小
const [fontSize, setFontSize] = useState(48);
- 初始值:48px,这是一个适中的大小
- 为什么是 48? 太小(如 24)在展示区不够醒目;太大(如 100)可能导致默认文字换行过多
- 可选范围:24、36、48、64、80、100,覆盖从"稍大"到"超大"的需求
🎨 bgColor 和 textColor - 背景色和文字色
const [bgColor, setBgColor] = useState('#0f0f23');
const [textColor, setTextColor] = useState('#fff');
- 为什么要成对管理? 背景色和文字色必须有足够对比度,否则看不清
- 初始配色:深蓝背景 + 白色文字,这是一个高对比度、低刺激的组合
- 设计考量:深色背景在暗光环境下不刺眼,白色文字清晰可见
四、动画系统实现
放大镜工具包含两套动画:入场动画和交互动画。
4.1 动画值初始化
const scaleAnim = useRef(new Animated.Value(1)).current;
const fadeAnim = useRef(new Animated.Value(0)).current;
代码解析:
| 动画值 | 初始值 | 用途 |
|---|---|---|
scaleAnim |
1 | 控制文字的缩放比例,用于字号切换动画 |
fadeAnim |
0 | 控制整个页面的透明度,用于入场动画 |
为什么用 useRef 而不是 useState?
这是 React Native 动画的一个重要模式:
// ❌ 错误做法
const [fadeAnim] = useState(new Animated.Value(0));
// ✅ 正确做法
const fadeAnim = useRef(new Animated.Value(0)).current;
原因:
Animated.Value是一个可变对象,它的值会在动画过程中不断变化- 如果用
useState,每次组件重渲染都可能创建新的Animated.Value useRef保证在组件整个生命周期内,引用的是同一个对象.current直接取出引用值,后续使用更简洁
4.2 入场动画
useEffect(() => {
Animated.timing(fadeAnim, { toValue: 1, duration: 500, useNativeDriver: true }).start();
}, []);
动画配置详解:
| 参数 | 值 | 含义 |
|---|---|---|
toValue |
1 | 目标透明度,1 表示完全不透明 |
duration |
500 | 动画时长 500ms,约半秒 |
useNativeDriver |
true | 使用原生驱动,性能更好 |
为什么入场动画很重要?
- 心理感受:页面"渐显"比"突然出现"更舒适
- 加载掩盖:如果有轻微的加载延迟,淡入动画可以掩盖这种"卡顿感"
- 品质感:细节动画是区分"精品应用"和"普通应用"的重要因素
useNativeDriver: true 的意义:
React Native 的动画有两种执行方式:
| 方式 | 执行位置 | 性能 | 限制 |
|---|---|---|---|
| JS 驱动 | JavaScript 线程 | 较低 | 无 |
| Native 驱动 | 原生 UI 线程 | 高 | 只支持 transform 和 opacity |
由于我们只动画 opacity,完全可以使用 Native 驱动,获得 60fps 的流畅体验。
4.3 字号切换动画
const animateFontChange = (newSize: number) => {
Animated.sequence([
Animated.timing(scaleAnim, { toValue: 0.9, duration: 100, useNativeDriver: true }),
Animated.spring(scaleAnim, { toValue: 1, friction: 4, useNativeDriver: true }),
]).start();
setFontSize(newSize);
};
动画序列解析:
这个函数实现了一个"按压回弹"的视觉效果,分为两个阶段:
阶段1: scale 1 → 0.9 (100ms, timing)
↓
阶段2: scale 0.9 → 1 (spring 弹性)
第一阶段:收缩
Animated.timing(scaleAnim, { toValue: 0.9, duration: 100, useNativeDriver: true })
- 文字快速缩小到 90%
- 使用
timing线性动画,100ms 很短,给人"被按下"的感觉
第二阶段:回弹
Animated.spring(scaleAnim, { toValue: 1, friction: 4, useNativeDriver: true })
- 文字弹回原始大小
- 使用
spring弹性动画,friction: 4控制阻尼 - 弹性动画会有轻微的"过冲"效果,更自然
friction 参数的影响:
| friction 值 | 效果 |
|---|---|
| 1-3 | 弹性很大,会明显过冲和回弹 |
| 4-6 | 适中的弹性,自然舒适 |
| 7+ | 几乎没有弹性,接近线性 |
这里选择 friction: 4 是一个平衡点:有弹性感,但不会过于夸张。
为什么 setFontSize 在动画外面?
Animated.sequence([...]).start();
setFontSize(newSize); // 立即执行,不等动画完成
状态更新是立即的,动画是视觉上的"装饰"。这样设计的好处:
- 用户点击后,字号立即生效
- 动画只是增强体验,不阻塞功能
五、主题颜色配置
5.1 颜色预设数组
const colors = [
{ bg: '#0f0f23', text: '#fff', name: '深蓝' },
{ bg: '#fff', text: '#000', name: '白色' },
{ bg: '#e74c3c', text: '#fff', name: '红色' },
{ bg: '#2ecc71', text: '#fff', name: '绿色' },
{ bg: '#3498db', text: '#fff', name: '蓝色' },
{ bg: '#f39c12', text: '#000', name: '橙色' },
];
颜色选择的设计考量:
| 主题 | 背景色 | 文字色 | 适用场景 |
|---|---|---|---|
| 深蓝 | #0f0f23 |
白 | 暗光环境,不刺眼 |
| 白色 | #fff |
黑 | 明亮环境,最高对比度 |
| 红色 | #e74c3c |
白 | 紧急信息,醒目 |
| 绿色 | #2ecc71 |
白 | 积极信息,舒适 |
| 蓝色 | #3498db |
白 | 通用场景,专业感 |
| 橙色 | #f39c12 |
黑 | 警示信息,温暖 |
为什么有的用白色文字,有的用黑色文字?
这涉及到颜色对比度的概念。根据 WCAG(Web 内容无障碍指南):
- 文字与背景的对比度至少要达到 4.5:1
- 大文字(18px 以上)可以放宽到 3:1
简单规则:
- 深色背景 → 浅色文字
- 浅色背景 → 深色文字
橙色 #f39c12 是一个中等亮度的颜色,配黑色文字比白色文字对比度更高。
为什么把颜色配置放在组件内部?
// 当前做法:组件内部定义
const colors = [...]
// 另一种做法:外部配置文件
import { MAGNIFIER_COLORS } from '../config/colors';
当前做法的优点:
- 代码集中,一个文件看完所有逻辑
- 颜色配置不太可能被其他组件复用
- 修改方便,不需要跨文件查找
如果未来需要支持用户自定义颜色,可以考虑提取到配置文件。
六、UI 布局实现
6.1 整体容器结构
return (
<Animated.View style={[styles.container, { opacity: fadeAnim }]}>
<View style={[styles.display, { backgroundColor: bgColor }]}>
{/* 展示区 */}
</View>
<View style={styles.controls}>
{/* 控制区 */}
</View>
</Animated.View>
);
布局结构解析:
┌─────────────────────────────┐
│ Animated.View │ ← 最外层,控制入场动画
│ ┌───────────────────────┐ │
│ │ display │ │ ← 展示区,flex: 1 占满剩余空间
│ │ (放大的文字) │ │
│ └───────────────────────┘ │
│ ┌───────────────────────┐ │
│ │ controls │ │ ← 控制区,固定高度
│ │ (输入框、按钮等) │ │
│ └───────────────────────┘ │
└─────────────────────────────┘
为什么用 Animated.View 包裹整个组件?
<Animated.View style={[styles.container, { opacity: fadeAnim }]}>
Animated.View是View的动画版本- 可以直接绑定
Animated.Value到样式属性 - 普通
View无法响应动画值的变化
动态样式的写法:
style={[styles.container, { opacity: fadeAnim }]}
这是 React Native 的样式合并语法:
styles.container:静态样式{ opacity: fadeAnim }:动态样式- 数组中后面的样式会覆盖前面的同名属性
6.2 展示区实现
<View style={[styles.display, { backgroundColor: bgColor }]}>
<ScrollView contentContainerStyle={styles.displayContent}>
<Animated.Text style={[styles.displayText, { fontSize, color: textColor, transform: [{ scale: scaleAnim }] }]}>
{text}
</Animated.Text>
</ScrollView>
</View>
层级结构详解:
| 层级 | 组件 | 职责 |
|---|---|---|
| 1 | View |
背景色容器,响应 bgColor 变化 |
| 2 | ScrollView |
当文字过长时提供滚动能力 |
| 3 | Animated.Text |
显示文字,响应字号和缩放动画 |
为什么需要 ScrollView?
考虑这个场景:
- 用户输入了一段很长的文字
- 字号设置为 100
- 屏幕宽度有限
如果没有 ScrollView,超出屏幕的文字会被截断。有了 ScrollView,用户可以滚动查看完整内容。
contentContainerStyle vs style 的区别:
<ScrollView
style={...} // ScrollView 自身的样式
contentContainerStyle={...} // 内容容器的样式
>
style:控制 ScrollView 这个"窗口"的大小和位置contentContainerStyle:控制内容的布局方式
这里我们用 contentContainerStyle 来居中文字:
displayContent: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 },
Animated.Text 的样式绑定:
style={[
styles.displayText, // 基础样式
{
fontSize, // 动态字号
color: textColor, // 动态颜色
transform: [{ scale: scaleAnim }] // 缩放动画
}
]}
transform 是一个数组,可以组合多种变换:
transform: [
{ scale: 1.2 }, // 缩放
{ rotate: '45deg' }, // 旋转
{ translateX: 10 }, // X 轴平移
]
这里只用了 scale,配合 scaleAnim 实现字号切换时的弹性效果。
6.3 控制区 - 文字输入
<View style={styles.inputSection}>
<Text style={styles.label}>📝 输入文字</Text>
<TextInput
style={styles.input}
value={text}
onChangeText={setText}
placeholder="输入要放大的文字"
placeholderTextColor="#666"
multiline
/>
</View>
TextInput 属性详解:
| 属性 | 值 | 作用 |
|---|---|---|
value |
text |
受控组件,显示当前状态值 |
onChangeText |
setText |
输入变化时更新状态 |
placeholder |
'输入要放大的文字' |
空内容时的提示文字 |
placeholderTextColor |
'#666' |
提示文字颜色,比正文浅 |
multiline |
true |
允许多行输入 |
受控组件 vs 非受控组件:
// 受控组件(当前做法)
<TextInput value={text} onChangeText={setText} />
// 非受控组件
<TextInput defaultValue="初始值" ref={inputRef} />
受控组件的优点:
- 状态集中管理,数据流清晰
- 可以在
onChangeText中做输入校验 - 方便实现"清空"、"重置"等功能
6.4 控制区 - 字号选择
<View style={styles.sizeControl}>
<Text style={styles.label}>🔤 字体大小: {fontSize}</Text>
<View style={styles.sizeButtons}>
{[24, 36, 48, 64, 80, 100].map(s => (
<TouchableOpacity
key={s}
style={[styles.sizeBtn, fontSize === s && styles.sizeBtnActive]}
onPress={() => animateFontChange(s)}
>
<Text style={[styles.sizeBtnText, fontSize === s && styles.sizeBtnTextActive]}>{s}</Text>
</TouchableOpacity>
))}
</View>
</View>
字号按钮的渲染逻辑:
{[24, 36, 48, 64, 80, 100].map(s => (
// 为每个字号生成一个按钮
))}
使用数组 map 而不是手写 6 个按钮的好处:
- 代码简洁,避免重复
- 修改字号选项只需改数组
- 保持一致的结构和样式
条件样式的写法:
style={[styles.sizeBtn, fontSize === s && styles.sizeBtnActive]}
这是 React Native 中常用的条件样式模式:
styles.sizeBtn:基础样式,始终应用fontSize === s && styles.sizeBtnActive:当条件为真时应用激活样式
&& 短路求值的特性:
- 如果
fontSize === s为false,整个表达式返回false - 如果
fontSize === s为true,返回styles.sizeBtnActive - React Native 的样式数组会忽略
false、null、undefined
为什么选择这 6 个字号?
| 字号 | 适用场景 |
|---|---|
| 24 | 稍大,适合段落文字 |
| 36 | 中等,适合标题 |
| 48 | 较大,默认值,平衡可读性和展示效果 |
| 64 | 大号,适合短语 |
| 80 | 超大,适合单词 |
| 100 | 巨大,适合数字或单个字符 |
这个梯度设计让用户可以根据内容长度选择合适的字号。
6.5 控制区 - 颜色选择
<View style={styles.colorControl}>
<Text style={styles.label}>🎨 主题颜色</Text>
<View style={styles.colorRow}>
{colors.map((c, i) => (
<TouchableOpacity
key={i}
style={[styles.colorBtn, { backgroundColor: c.bg }, bgColor === c.bg && styles.colorBtnActive]}
onPress={() => { setBgColor(c.bg); setTextColor(c.text); }}
>
<Text style={[styles.colorName, { color: c.text }]}>{c.name}</Text>
</TouchableOpacity>
))}
</View>
</View>
颜色按钮的视觉设计:
每个颜色按钮本身就是一个"预览":
- 按钮背景色 = 主题背景色
- 按钮文字色 = 主题文字色
- 用户可以直观看到选择后的效果
激活状态的视觉反馈:
bgColor === c.bg && styles.colorBtnActive
colorBtnActive: { borderColor: '#ffd700' }, // 金色边框
当某个颜色被选中时,显示金色边框。为什么用金色?
- 金色在各种背景色上都比较醒目
- 不会与任何主题颜色冲突
- 给人"选中"、"高亮"的感觉
同时更新两个状态:
onPress={() => { setBgColor(c.bg); setTextColor(c.text); }}
这里在一个事件处理函数中调用了两个 setState。React 会将它们批量处理,只触发一次重渲染。
七、样式系统详解
7.1 容器样式
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0f0f23' },
display: { flex: 1, justifyContent: 'center', alignItems: 'center' },
displayContent: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 },
displayText: { textAlign: 'center', fontWeight: '600' },
Flex 布局解析:
container (flex: 1)
├── display (flex: 1) ← 占据所有剩余空间
└── controls (无 flex) ← 由内容撑开高度
flex: 1 的含义:
- 在父容器中占据所有可用空间
- 如果有多个
flex: 1的兄弟元素,它们平分空间
这里只有 display 设置了 flex: 1,所以它会占据除 controls 之外的所有空间。
文字样式:
displayText: { textAlign: 'center', fontWeight: '600' },
textAlign: 'center':文字水平居中fontWeight: '600':半粗体,比普通文字更醒目
7.2 控制区样式
controls: {
backgroundColor: '#1a1a3e',
padding: 16,
borderTopLeftRadius: 24,
borderTopRightRadius: 24,
borderWidth: 1,
borderColor: '#3a3a6a',
borderBottomWidth: 0
},
设计细节解析:
| 属性 | 值 | 设计意图 |
|---|---|---|
backgroundColor |
#1a1a3e |
比主背景稍浅,形成层次感 |
padding |
16 | 内边距,让内容不贴边 |
borderTopLeftRadius |
24 | 左上圆角 |
borderTopRightRadius |
24 | 右上圆角 |
borderWidth |
1 | 边框宽度 |
borderColor |
#3a3a6a |
边框颜色,微妙的分隔线 |
borderBottomWidth |
0 | 底部无边框,因为贴着屏幕底部 |
为什么只有顶部圆角?
控制区是一个"从底部弹出"的面板视觉效果:
┌────────────────────────┐
│ │
│ 展示区 │
│ │
├────────────────────────┤ ← 这里有圆角
│ 控制区 │
└────────────────────────┘ ← 这里贴着屏幕底部,无圆角
这种设计在 iOS 和 Material Design 中都很常见,给人"卡片浮起"的感觉。
7.3 输入框样式
input: {
backgroundColor: '#252550',
padding: 12,
borderRadius: 12,
color: '#fff',
borderWidth: 1,
borderColor: '#3a3a6a'
},
输入框的视觉层次:
控制区背景: #1a1a3e (最深)
↓
输入框背景: #252550 (稍浅)
↓
输入框边框: #3a3a6a (最浅)
这种"深 → 浅"的层次让输入框在控制区中"凸显"出来,引导用户注意。
7.4 按钮样式
sizeBtn: {
padding: 10,
marginRight: 8,
marginBottom: 8,
backgroundColor: '#252550',
borderRadius: 8,
minWidth: 50,
alignItems: 'center',
borderWidth: 1,
borderColor: '#3a3a6a'
},
sizeBtnActive: { backgroundColor: '#4A90D9', borderColor: '#4A90D9' },
sizeBtnText: { color: '#888' },
sizeBtnTextActive: { color: '#fff', fontWeight: '600' },
按钮状态对比:
| 状态 | 背景色 | 边框色 | 文字色 | 文字粗细 |
|---|---|---|---|---|
| 默认 | #252550 |
#3a3a6a |
#888 |
普通 |
| 激活 | #4A90D9 |
#4A90D9 |
#fff |
600 |
激活状态使用蓝色高亮,与默认状态形成明显对比,用户一眼就能看出当前选择。
7.5 颜色按钮样式
colorBtn: {
width: 50,
height: 50,
borderRadius: 12,
marginRight: 8,
marginBottom: 8,
borderWidth: 2,
borderColor: 'transparent',
justifyContent: 'center',
alignItems: 'center'
},
colorBtnActive: { borderColor: '#ffd700' },
colorName: { fontSize: 10, fontWeight: '600' },
颜色按钮的设计技巧:
- 固定尺寸:
width: 50, height: 50,保证所有颜色按钮大小一致 - 圆角:
borderRadius: 12,柔和的圆角,不是完全的圆形 - 透明边框:默认
borderColor: 'transparent',激活时变金色 - 小字号:
fontSize: 10,颜色名称不喧宾夺主
为什么默认边框是透明而不是无边框?
// 当前做法
borderWidth: 2,
borderColor: 'transparent',
// 另一种做法
borderWidth: 0, // 默认无边框
// 激活时
borderWidth: 2,
borderColor: '#ffd700',
如果默认无边框,激活时突然出现边框会导致按钮"跳动"(因为边框占用空间)。
使用透明边框可以保持布局稳定,只改变颜色。
八、完整代码回顾
将所有部分组合起来,这是 src/pages/Magnifier.tsx 的完整代码:
import React, { useState, useRef, useEffect } from 'react';
import { View, Text, TextInput, StyleSheet, ScrollView, TouchableOpacity, Animated } from 'react-native';
export const Magnifier: React.FC = () => {
const [text, setText] = useState('在此输入文字');
const [fontSize, setFontSize] = useState(48);
const [bgColor, setBgColor] = useState('#0f0f23');
const [textColor, setTextColor] = useState('#fff');
const scaleAnim = useRef(new Animated.Value(1)).current;
const fadeAnim = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.timing(fadeAnim, { toValue: 1, duration: 500, useNativeDriver: true }).start();
}, []);
const animateFontChange = (newSize: number) => {
Animated.sequence([
Animated.timing(scaleAnim, { toValue: 0.9, duration: 100, useNativeDriver: true }),
Animated.spring(scaleAnim, { toValue: 1, friction: 4, useNativeDriver: true }),
]).start();
setFontSize(newSize);
};
const colors = [
{ bg: '#0f0f23', text: '#fff', name: '深蓝' },
{ bg: '#fff', text: '#000', name: '白色' },
{ bg: '#e74c3c', text: '#fff', name: '红色' },
{ bg: '#2ecc71', text: '#fff', name: '绿色' },
{ bg: '#3498db', text: '#fff', name: '蓝色' },
{ bg: '#f39c12', text: '#000', name: '橙色' },
];
return (
<Animated.View style={[styles.container, { opacity: fadeAnim }]}>
<View style={[styles.display, { backgroundColor: bgColor }]}>
<ScrollView contentContainerStyle={styles.displayContent}>
<Animated.Text style={[styles.displayText, { fontSize, color: textColor, transform: [{ scale: scaleAnim }] }]}>
{text}
</Animated.Text>
</ScrollView>
</View>
<View style={styles.controls}>
<View style={styles.inputSection}>
<Text style={styles.label}>📝 输入文字</Text>
<TextInput style={styles.input} value={text} onChangeText={setText} placeholder="输入要放大的文字" placeholderTextColor="#666" multiline />
</View>
<View style={styles.sizeControl}>
<Text style={styles.label}>🔤 字体大小: {fontSize}</Text>
<View style={styles.sizeButtons}>
{[24, 36, 48, 64, 80, 100].map(s => (
<TouchableOpacity key={s} style={[styles.sizeBtn, fontSize === s && styles.sizeBtnActive]} onPress={() => animateFontChange(s)}>
<Text style={[styles.sizeBtnText, fontSize === s && styles.sizeBtnTextActive]}>{s}</Text>
</TouchableOpacity>
))}
</View>
</View>
<View style={styles.colorControl}>
<Text style={styles.label}>🎨 主题颜色</Text>
<View style={styles.colorRow}>
{colors.map((c, i) => (
<TouchableOpacity key={i} style={[styles.colorBtn, { backgroundColor: c.bg }, bgColor === c.bg && styles.colorBtnActive]} onPress={() => { setBgColor(c.bg); setTextColor(c.text); }}>
<Text style={[styles.colorName, { color: c.text }]}>{c.name}</Text>
</TouchableOpacity>
))}
</View>
</View>
</View>
</Animated.View>
);
};
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0f0f23' },
display: { flex: 1, justifyContent: 'center', alignItems: 'center' },
displayContent: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 },
displayText: { textAlign: 'center', fontWeight: '600' },
controls: { backgroundColor: '#1a1a3e', padding: 16, borderTopLeftRadius: 24, borderTopRightRadius: 24, borderWidth: 1, borderColor: '#3a3a6a', borderBottomWidth: 0 },
inputSection: { marginBottom: 16 },
label: { fontSize: 14, color: '#888', marginBottom: 8 },
input: { backgroundColor: '#252550', padding: 12, borderRadius: 12, color: '#fff', borderWidth: 1, borderColor: '#3a3a6a' },
sizeControl: { marginBottom: 16 },
sizeButtons: { flexDirection: 'row', flexWrap: 'wrap' },
sizeBtn: { padding: 10, marginRight: 8, marginBottom: 8, backgroundColor: '#252550', borderRadius: 8, minWidth: 50, alignItems: 'center', borderWidth: 1, borderColor: '#3a3a6a' },
sizeBtnActive: { backgroundColor: '#4A90D9', borderColor: '#4A90D9' },
sizeBtnText: { color: '#888' },
sizeBtnTextActive: { color: '#fff', fontWeight: '600' },
colorControl: { marginBottom: 8 },
colorRow: { flexDirection: 'row', flexWrap: 'wrap' },
colorBtn: { width: 50, height: 50, borderRadius: 12, marginRight: 8, marginBottom: 8, borderWidth: 2, borderColor: 'transparent', justifyContent: 'center', alignItems: 'center' },
colorBtnActive: { borderColor: '#ffd700' },
colorName: { fontSize: 10, fontWeight: '600' },
});
九、功能扩展思路
当前实现已经满足基本需求,如果想进一步完善,可以考虑:
9.1 功能增强
| 功能 | 实现思路 |
|---|---|
| 全屏模式 | 隐藏控制区,双击切换 |
| 自定义颜色 | 添加颜色选择器组件 |
| 字体选择 | 支持多种字体风格 |
| 文字阴影 | 增加 textShadow 样式 |
| 滚动字幕 | 使用 Animated 实现水平滚动 |
9.2 体验优化
| 优化点 | 实现思路 |
|---|---|
| 屏幕常亮 | 使用 react-native-keep-awake |
| 手势缩放 | 使用 PanResponder 或 react-native-gesture-handler |
| 历史记录 | 使用 AsyncStorage 保存常用文字 |
| 分享功能 | 截图并调用系统分享 |
9.3 无障碍支持
<TouchableOpacity
accessible={true}
accessibilityLabel={`字体大小 ${s}`}
accessibilityRole="button"
accessibilityState={{ selected: fontSize === s }}
>
添加无障碍属性,让视障用户也能使用屏幕阅读器操作。
十、总结
本文实现的「文字放大镜」工具,虽然功能简单,但涵盖了 React Native 开发的多个核心知识点:
| 知识点 | 本文应用 |
|---|---|
| 状态管理 | 4 个 useState 管理文字、字号、颜色 |
| 动画系统 | timing、spring、sequence 组合使用 |
| 条件样式 | && 短路求值实现激活状态 |
| Flex 布局 | 展示区自适应 + 控制区固定 |
| 受控组件 | TextInput 的 value + onChangeText |
| 列表渲染 | map 生成按钮组 |
这个工具的代码量不大(约 80 行),但每一行都有其设计考量。希望通过本文的详细解析,能帮助你理解 React Native 组件开发的思路和技巧。
相关资源
- 📦 项目源码:
src/pages/Magnifier.tsx - 🔧 工具配置:
src/tools/index.ts - 🗺️ 路由映射:
src/screens/ToolScreen.tsx
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)