在这里插入图片描述

今天我们用 React Native 实现一个空格处理工具,支持 6 种常用的空白字符处理方式(去除所有空格、去除首尾空格、去除多余空格等),还能实时统计空白字符的数量。

状态设计

import React, { useState, useRef, useEffect } from 'react';
import { View, Text, TextInput, TouchableOpacity, StyleSheet, ScrollView, Animated } from 'react-native';

export const SpaceRemover: React.FC = () => {
  const [text, setText] = useState('');
  const buttonAnims = useRef(Array(6).fill(0).map(() => new Animated.Value(1))).current;
  const statsAnim = useRef(new Animated.Value(1)).current;
  const pulseAnim = useRef(new Animated.Value(1)).current;

状态设计包含一个状态变量和三组动画值:

文本内容 text:存储用户输入的文本,也是所有处理操作的目标。初始为空字符串。

按钮动画数组 buttonAnims:6 个处理按钮各有一个独立的动画值,初始都是 1(正常大小)。为什么需要 6 个独立的动画值?因为用户可能快速点击不同的按钮,每个按钮的动画应该独立播放,互不干扰。如果共用一个动画值,第二次点击会打断第一次的动画,看起来很不流畅。

统计区动画 statsAnim:统计卡片的缩放动画,初始值 1。当用户点击处理按钮时,文本内容变化,统计数据也会变化,这时触发统计区的动画,提示用户"数据已更新"。

脉冲动画 pulseAnim:虽然在代码中定义了,但在当前版本中没有使用。这是为未来扩展预留的,比如可以给输入框或统计区添加呼吸动画。

Array(6).fill(0).map(() => new Animated.Value(1)) 创建 6 个独立的动画值对象。不能用 Array(6).fill(new Animated.Value(1)),因为那样会让所有元素共享同一个对象,修改一个就会影响其他的。

脉冲动画初始化

  useEffect(() => {
    Animated.loop(
      Animated.sequence([
        Animated.timing(pulseAnim, { toValue: 1.05, duration: 1000, useNativeDriver: true }),
        Animated.timing(pulseAnim, { toValue: 1, duration: 1000, useNativeDriver: true }),
      ])
    ).start();
  }, []);

组件挂载时启动脉冲动画,这是一个无限循环的呼吸动画。

动画序列:先放大到 105%(1 秒),再缩小到 100%(1 秒),总共 2 秒一个周期。Animated.sequence 让两个动画依次执行,Animated.loop 让序列无限循环。

时长选择:1 秒的时长比较舒缓,不会让用户感到烦躁。如果太快(比如 300ms),会显得很急促,像在闪烁;如果太慢(比如 3 秒),变化太慢,用户可能注意不到。

缩放幅度:1.05 表示放大 5%,这是一个很微妙的变化。太大会干扰用户阅读,太小又看不出效果。5% 是一个经过实践检验的"黄金比例"。

原生驱动useNativeDriver: true 让动画在原生层执行,不占用 JavaScript 线程,即使主线程很忙,动画也能保持 60fps 流畅运行。

依赖数组[] 表示这个 effect 只在组件挂载时执行一次。如果不传依赖数组,每次渲染都会重新启动动画,导致动画重复播放。如果传入 [pulseAnim],由于 pulseAnim 是 ref 不会变化,效果和 [] 一样。

虽然当前版本没有使用这个动画,但代码已经准备好了,未来只需要把 pulseAnim 应用到某个元素上就能看到效果。

按钮动画函数

  const animateButton = (index: number) => {
    Animated.sequence([
      Animated.timing(buttonAnims[index], { toValue: 0.9, duration: 100, useNativeDriver: true }),
      Animated.spring(buttonAnims[index], { toValue: 1, friction: 3, useNativeDriver: true }),
    ]).start();
    
    Animated.sequence([
      Animated.timing(statsAnim, { toValue: 0.95, duration: 100, useNativeDriver: true }),
      Animated.spring(statsAnim, { toValue: 1, friction: 4, useNativeDriver: true }),
    ]).start();
  };

这个函数在用户点击处理按钮时触发,同时播放两个动画:按钮动画和统计区动画。

按钮动画的工作原理

  • 第一阶段:用 Animated.timing 在 100 毫秒内把按钮缩小到 90%。这个阶段模拟"按下"的动作,按钮被压缩了。
  • 第二阶段:用 Animated.spring 弹回到原大小(100%)。friction: 3 设置摩擦力,值越小弹性越大,按钮会有明显的"回弹"效果。

为什么用弹簧动画而不是线性动画?因为弹簧动画更符合物理直觉。真实世界中,按下一个按钮后松手,按钮会弹回原位,甚至会有轻微的震动。弹簧动画模拟了这个物理过程,让交互更自然。

统计区动画的工作原理

  • 同样是先缩小(95%)再弹回(100%)
  • friction: 4 比按钮的摩擦力大,弹性稍弱,动画更稳定

为什么统计区缩小到 95% 而按钮缩小到 90%?因为统计区比按钮大,相同的缩放比例在大元素上更明显。如果统计区也缩小到 90%,会显得太夸张,像是整个区域在"跳动"。95% 的缩放刚刚好,既能让用户注意到变化,又不会太突兀。

两个动画的关系

  • 它们同时启动(两个 start() 调用没有等待)
  • 但作用于不同的元素(按钮 vs 统计区)
  • 用户看到的效果是:点击按钮 → 按钮缩小弹回 → 统计区同时缩小弹回

这种组合动画给用户明确的反馈:“我点击了这个按钮,文本已处理,统计数据已更新”。三个信息通过一次交互传达给用户,这就是好的交互设计。

处理函数定义

  const operations = [
    { name: '去除所有空格', icon: '🚫', fn: (s: string) => s.replace(/\s/g, '') },
    { name: '去除首尾空格', icon: '✂️', fn: (s: string) => s.trim() },
    { name: '去除多余空格', icon: '📏', fn: (s: string) => s.replace(/\s+/g, ' ').trim() },
    { name: '去除空行', icon: '📄', fn: (s: string) => s.split('\n').filter(line => line.trim()).join('\n') },
    { name: '每行去首尾', icon: '📋', fn: (s: string) => s.split('\n').map(line => line.trim()).join('\n') },
    { name: '去除制表符', icon: '⌨️', fn: (s: string) => s.replace(/\t/g, '') },
  ];

这个数组定义了 6 种空白字符处理方式,每种包含名称、图标、处理函数。这种数据驱动的设计让代码非常简洁,添加新的处理方式只需要在数组里加一项。

去除所有空格/\s/g 匹配所有空白字符(空格、制表符、换行符等),g 表示全局匹配,replace 把它们全部替换成空字符串。比如 "Hello World\n" 变成 "HelloWorld"

这个操作最激进,会删除所有空白字符,包括换行。适用于需要紧凑格式的场景,比如生成 URL slug、数据库查询等。但要注意,删除换行符可能会让多行文本变成一行,影响可读性。

去除首尾空格trim() 是 JavaScript 字符串的内置方法,删除字符串开头和结尾的空白字符,但保留中间的空白。比如 " Hello World " 变成 "Hello World"

这是最常用的操作,用于清理用户输入。用户在输入框里输入文本时,可能不小心在开头或结尾多打了空格,trim() 可以自动清理这些多余的空格。很多表单验证都会先 trim() 再检查内容。

去除多余空格/\s+/g 匹配一个或多个连续的空白字符,replace 把它们替换成一个空格,最后用 trim() 删除首尾空格。比如 "Hello World " 变成 "Hello World"

这个操作保留了单词之间的空格,但把多个连续空格合并成一个。适用于格式化文本,让文本看起来更整洁。从网页复制的文本经常有多余的空格,用这个操作可以快速清理。

去除空行:先用 split('\n') 按换行符分割成行数组,然后用 filter(line => line.trim()) 过滤掉空行(trim() 后为空字符串的行),最后用 join('\n') 拼回字符串。

什么是空行?就是只包含空白字符(空格、制表符)的行,或者完全空的行。line.trim() 删除行首尾空白后,如果结果是空字符串,说明这行是空行。filter 只保留 trim() 后非空的行。

比如:

Hello

World


!

变成:

Hello
World
!

这个操作常用于清理代码、文章等多行文本,删除多余的空行让内容更紧凑。

每行去首尾:和"去除空行"类似,也是先 split('\n') 分割成行,但用 map(line => line.trim()) 对每行执行 trim(),最后 join('\n') 拼回去。

这个操作保留所有行(包括空行),但删除每行首尾的空白字符。比如:

  Hello  
    World  

变成:

Hello
World

适用于清理缩进混乱的文本,或者从代码编辑器复制的文本(可能有缩进)。

去除制表符/\t/g 匹配所有制表符,replace 把它们替换成空字符串。比如 "Hello\tWorld" 变成 "HelloWorld"

制表符在不同编辑器中显示宽度不同(有的是 4 个空格,有的是 8 个),导致格式混乱。这个操作直接删除所有制表符,避免格式问题。如果想把制表符替换成空格,可以改成 s.replace(/\t/g, ' ')(4 个空格)。

统计指标定义

  const stats = [
    { icon: '⬜', value: (text.match(/ /g) || []).length, label: '空格' },
    { icon: '⏩', value: (text.match(/\t/g) || []).length, label: '制表符' },
    { icon: '↩️', value: (text.match(/\n/g) || []).length, label: '换行符' },
  ];

这个数组定义了 3 个统计指标,实时显示文本中空白字符的数量。

空格统计/ /g 匹配所有空格字符(注意正则中间是一个空格),match() 返回匹配数组,数组长度就是空格数量。如果没有匹配(match() 返回 null),用 || [] 提供默认值空数组,避免 null.length 报错。

为什么不用 /\s/g 匹配所有空白字符?因为我们要分别统计空格、制表符、换行符,如果用 \s 会把它们全部匹配,无法区分。

制表符统计/\t/g 匹配所有制表符。\t 是制表符的转义表示,在字符串中写 "\t" 就是一个制表符。

制表符在文本中不可见(或者显示为很宽的空白),但确实存在。统计制表符的数量可以帮助用户发现隐藏的格式问题。比如代码缩进混用空格和制表符,看起来对齐了,但实际上格式不一致。

换行符统计/\n/g 匹配所有换行符。换行符的数量通常等于行数减 1(最后一行后面没有换行符)。

为什么要统计换行符?因为有些场景对行数有限制,比如短信、推特等。统计换行符可以帮助用户了解文本的结构。

实时计算 vs 缓存:这三个统计指标每次渲染都会重新计算,会不会影响性能?实际上不会,因为:

  1. 正则匹配的性能很好,即使几千字的文本也是瞬间完成
  2. 用户输入的频率不高,每秒最多几十次
  3. React 的虚拟 DOM 会优化渲染,只更新变化的部分

如果真的遇到性能问题(比如处理几万字的文本),可以用 useMemo 缓存计算结果:

const stats = useMemo(() => [
  { icon: '⬜', value: (text.match(/ /g) || []).length, label: '空格' },
  // ...
], [text]);

但在这个场景下,过度优化反而会增加代码复杂度。

鸿蒙 ArkTS 对比:处理函数

operations = [
  { name: '去除所有空格', icon: '🚫', fn: (s: string) => s.replace(/\s/g, '') },
  { name: '去除首尾空格', icon: '✂️', fn: (s: string) => s.trim() },
  { name: '去除多余空格', icon: '📏', fn: (s: string) => s.replace(/\s+/g, ' ').trim() },
  { name: '去除空行', icon: '📄', fn: (s: string) => s.split('\n').filter(line => line.trim()).join('\n') },
  { name: '每行去首尾', icon: '📋', fn: (s: string) => s.split('\n').map(line => line.trim()).join('\n') },
  { name: '去除制表符', icon: '⌨️', fn: (s: string) => s.replace(/\t/g, '') },
]

get stats() {
  return [
    { icon: '⬜', value: (this.text.match(/ /g) || []).length, label: '空格' },
    { icon: '⏩', value: (this.text.match(/\t/g) || []).length, label: '制表符' },
    { icon: '↩️', value: (this.text.match(/\n/g) || []).length, label: '换行符' },
  ]
}

ArkTS 中的处理函数和统计逻辑完全一样,因为字符串方法、正则表达式、数组方法都是 JavaScript 标准 API,跨平台通用。这就是 React Native 的优势——核心业务逻辑可以直接复用,不需要为每个平台重写。

界面渲染:头部和输入框

  return (
    <ScrollView style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.headerIcon}>✂️</Text>
        <Text style={styles.headerTitle}>去除空格</Text>
        <Text style={styles.headerSubtitle}>多种空格处理方式</Text>
      </View>

      <View style={styles.inputCard}>
        <TextInput
          style={styles.input}
          value={text}
          onChangeText={setText}
          multiline
          placeholder="输入要处理的文本..."
          placeholderTextColor="#666"
        />
      </View>

头部区域包含图标、标题、副标题,和其他工具保持一致的风格。✂️ 剪刀图标表示"剪掉"多余空格的概念,直观易懂。

输入框设计

  • View 包裹 TextInput,形成双层结构(卡片 + 输入框)
  • multiline 让输入框支持多行输入,因为空格处理经常涉及多行文本
  • placeholder 提示用户"输入要处理的文本",明确告诉用户这个工具的用法
  • placeholderTextColor="#666" 设置占位符颜色为灰色,和实际输入的白色文本形成对比

为什么不给输入框加动画?因为输入框是用户交互的主要区域,如果加动画(比如缩放、抖动),会干扰用户输入。想象一下,你正在输入文本,输入框突然缩小又放大,光标位置可能会跳动,输入体验很差。

所以我们把动画应用在统计区和按钮上,这些区域不涉及用户输入,动画不会造成干扰。

统计区渲染

      <Animated.View style={[styles.statsRow, { transform: [{ scale: statsAnim }] }]}>
        {stats.map((stat, i) => (
          <View key={i} style={styles.statItem}>
            <Text style={styles.statIcon}>{stat.icon}</Text>
            <Text style={styles.statValue}>{stat.value}</Text>
            <Text style={styles.statLabel}>{stat.label}</Text>
          </View>
        ))}
      </Animated.View>

统计区用 Animated.View 包裹,应用缩放动画。当用户点击处理按钮时,文本内容变化,统计数据也会变化,这时触发动画,提示用户"数据已更新"。

横向布局flexDirection: 'row' 让三个统计项横向排列,flex: 1 让它们平均分配宽度。这样无论屏幕多宽,三个统计项总是等宽的。

统计项内容

  • 顶部是 emoji 图标(⬜ 空格、⏩ 制表符、↩️ 换行符),24 号字体
  • 中间是数值,24 号粗体,蓝色(#4A90D9),是视觉焦点
  • 底部是标签,12 号灰色文字,说明这个数值的含义

为什么用 emoji 图标?因为 emoji 是跨平台的,不需要导入图片资源,也不需要字体文件。而且 emoji 的含义很直观,用户一眼就能理解。⬜ 表示空白,⏩ 表示快进(制表符让光标快速移动),↩️ 表示回车换行。

数值颜色:用蓝色而不是白色,是为了突出显示。蓝色在深色背景上很醒目,而且给人"科技感"的印象。如果用白色,数值会和标签混在一起,不够突出。

处理按钮渲染

      <View style={styles.buttons}>
        {operations.map(({ name, icon, fn }, index) => (
          <Animated.View
            key={name}
            style={[styles.btnWrapper, { transform: [{ scale: buttonAnims[index] }] }]}
          >
            <TouchableOpacity
              style={styles.btn}
              onPress={() => { animateButton(index); setText(fn(text)); }}
              activeOpacity={0.8}
            >
              <Text style={styles.btnIcon}>{icon}</Text>
              <Text style={styles.btnText}>{name}</Text>
            </TouchableOpacity>
          </Animated.View>
        ))}
      </View>
    </ScrollView>
  );
};

按钮区域用网格布局,每行两个按钮。map 遍历 operations 数组,为每种处理方式生成一个按钮。

解构赋值{ name, icon, fn } 从数组元素中提取需要的属性,index 是数组索引,用于访问对应的动画值。这种写法让代码更简洁,不需要写 operations[index].name

动画包裹:每个按钮用 Animated.View 包裹,应用独立的缩放动画。key={name} 是 React 列表渲染的要求,用唯一的 key 标识每个元素。为什么用 name 而不是 index?因为 name 是唯一的,而且不会变化。如果用 index,当数组顺序改变时,React 可能会错误地复用组件。

点击处理onPress 做了两件事:

  1. animateButton(index):触发按钮和统计区的动画
  2. setText(fn(text)):调用处理函数,把结果更新到状态

这两个操作的顺序很重要。先触发动画,再更新文本,这样动画和文本更新几乎同时发生,用户感觉很流畅。如果先更新文本再触发动画,可能会有轻微的延迟感。

按钮内容:图标和文字横向排列,图标在左,文字在右。marginRight: 8 在它们之间留出间距,避免粘在一起。

触摸反馈activeOpacity={0.8} 让按钮按下时稍微变暗,配合缩放动画,给用户双重反馈。视觉上,按钮变暗了;动画上,按钮缩小了。这种多重反馈让用户确信"我的操作已经生效"。

鸿蒙 ArkTS 对比:界面渲染

Column() {
  // 头部
  Column() {
    Text('✂️').fontSize(50).margin({ bottom: 8 })
    Text('去除空格').fontSize(28).fontWeight(FontWeight.Bold).fontColor(Color.White)
    Text('多种空格处理方式').fontSize(14).fontColor('#888888').margin({ top: 4 })
  }
  .width('100%')
  .alignItems(HorizontalAlign.Center)
  .margin({ bottom: 24 })
  
  // 输入框
  TextArea({ text: this.text, placeholder: '输入要处理的文本...' })
    .width('100%')
    .height(150)
    .backgroundColor('#252550')
    .borderRadius(16)
    .onChange((value: string) => {
      this.text = value
    })
  
  // 统计区
  Row() {
    ForEach(this.stats, (stat: StatItem) => {
      Column() {
        Text(stat.icon).fontSize(24).margin({ bottom: 4 })
        Text(stat.value.toString()).fontSize(24).fontWeight(FontWeight.Bold).fontColor('#4A90D9')
        Text(stat.label).fontSize(12).fontColor('#888888').margin({ top: 4 })
      }
      .layoutWeight(1)
      .alignItems(HorizontalAlign.Center)
    })
  }
  .width('100%')
  .backgroundColor('#1a1a3e')
  .borderRadius(16)
  .padding(16)
  .scale({ x: this.statsScale, y: this.statsScale })
  
  // 按钮
  Grid() {
    ForEach(this.operations, (op: Operation, index: number) => {
      GridItem() {
        Button() {
          Row() {
            Text(op.icon).fontSize(18).margin({ right: 8 })
            Text(op.name).fontSize(13).fontWeight(FontWeight.Medium)
          }
        }
        .width('100%')
        .backgroundColor('#1a1a3e')
        .onClick(() => {
          this.animateButton(index)
          this.text = op.fn(this.text)
        })
        .scale({ x: this.buttonScales[index], y: this.buttonScales[index] })
      }
    })
  }
  .columnsTemplate('1fr 1fr')
  .rowsGap(8)
  .columnsGap(8)
}

鸿蒙的布局结构和 React Native 类似,只是语法不同。Column 对应 View(垂直布局),Row 对应 View(水平布局),TextArea 对应 TextInputGrid 对应网格布局。

核心逻辑(处理函数、统计计算)完全一样,只是界面代码需要适配鸿蒙的组件和 API。

样式定义:容器和头部

const styles = StyleSheet.create({
  container: { flex: 1, backgroundColor: '#0f0f23', padding: 20 },
  header: { alignItems: 'center', marginBottom: 24 },
  headerIcon: { fontSize: 50, marginBottom: 8 },
  headerTitle: { fontSize: 28, fontWeight: '700', color: '#fff' },
  headerSubtitle: { fontSize: 14, color: '#888', marginTop: 4 },

容器用深蓝色背景 #0f0f23,这是整个应用的统一配色。padding: 20 在四周留出边距,让内容不会紧贴屏幕边缘,看起来更舒适。

头部元素全部居中对齐(alignItems: 'center'),图标、标题、副标题垂直排列。字号从大到小(50 → 28 → 14),颜色从亮到暗(emoji → 白色 → 灰色),形成清晰的视觉层次,引导用户的视线从上到下阅读。

marginBottom: 24 在头部和输入框之间留出间距,避免元素挤在一起。24 像素是一个常用的间距值,不会太大也不会太小。

样式定义:输入框

  inputCard: {
    backgroundColor: '#1a1a3e',
    borderRadius: 20,
    padding: 4,
    marginBottom: 20,
    borderWidth: 1,
    borderColor: '#3a3a6a',
  },
  input: {
    backgroundColor: '#252550',
    padding: 16,
    borderRadius: 16,
    minHeight: 150,
    fontSize: 16,
    color: '#fff',
  },

输入框用双层结构:外层是卡片容器(inputCard),内层是实际的输入框(input)。

卡片容器的设计

  • 背景色 #1a1a3e 比容器背景稍亮,形成层次感
  • 圆角 20 让卡片看起来柔和,不会太生硬
  • 内边距 4 在卡片和输入框之间留出一圈间隙,形成"内嵌"效果
  • 边框颜色 #3a3a6a 比背景更亮,让卡片有立体感,像是"浮"在背景上

输入框的设计

  • 背景色 #252550 比卡片背景更深,和卡片形成对比,让用户知道"这是可以输入的区域"
  • 圆角 16 比卡片的圆角 20 小,这样四个角不会超出卡片
  • 最小高度 150 像素,确保输入框有足够的空间显示多行文本
  • 字号 16,大小适中,既不会太小看不清,也不会太大占空间
  • 白色文字(color: '#fff')在深色背景上很醒目

为什么要用双层结构?因为这样可以在卡片和输入框之间留出间隙,形成"内嵌"效果。如果直接给输入框加边框,效果不如双层结构好看。这是一个常见的 UI 设计技巧。

样式定义:统计区

  statsRow: {
    flexDirection: 'row',
    backgroundColor: '#1a1a3e',
    borderRadius: 16,
    padding: 16,
    marginBottom: 20,
    borderWidth: 1,
    borderColor: '#3a3a6a',
  },
  statItem: { flex: 1, alignItems: 'center' },
  statIcon: { fontSize: 24, marginBottom: 4 },
  statValue: { fontSize: 24, fontWeight: '700', color: '#4A90D9' },
  statLabel: { fontSize: 12, color: '#888', marginTop: 4 },

统计区是一个横向布局的卡片,包含三个统计项。

统计区容器

  • flexDirection: 'row' 让三个统计项横向排列
  • 背景色、圆角、边框和输入框卡片一致,保持视觉统一
  • 内边距 16 让内容不会紧贴边缘

统计项布局

  • flex: 1 让三个统计项平均分配宽度,无论屏幕多宽,它们总是等宽的
  • alignItems: 'center' 让图标、数值、标签垂直居中对齐

统计项内容

  • 图标 24 号字体,比较醒目
  • 数值 24 号粗体,蓝色(#4A90D9),是视觉焦点。为什么用蓝色?因为蓝色在深色背景上很醒目,而且给人"科技感"的印象
  • 标签 12 号灰色文字,比数值小,起辅助说明作用

图标、数值、标签之间用 marginBottommarginTop 留出间距,避免挤在一起。4 像素的间距很小,但足以让元素分开。

样式定义:按钮

  buttons: { flexDirection: 'row', flexWrap: 'wrap' },
  btnWrapper: { width: '48%', margin: '1%' },
  btn: {
    backgroundColor: '#1a1a3e',
    padding: 16,
    borderRadius: 12,
    alignItems: 'center',
    flexDirection: 'row',
    justifyContent: 'center',
    borderWidth: 1,
    borderColor: '#3a3a6a',
  },
  btnIcon: { fontSize: 18, marginRight: 8 },
  btnText: { color: '#fff', fontSize: 13, fontWeight: '600' },
});

按钮区域用 Flexbox 布局,flexWrap: 'wrap' 让按钮自动换行。

按钮包裹器的尺寸计算

  • 宽度 48%,左右各留 1% 的外边距
  • 两个按钮加起来:48% + 1% + 1% + 48% + 1% + 1% = 100%,正好占满一行
  • 这种百分比布局可以自适应不同屏幕宽度

为什么不用固定像素宽度?因为不同设备的屏幕宽度不同。iPhone SE 的屏幕宽度是 320px,iPad 的屏幕宽度是 768px。如果用固定宽度,在小屏幕上按钮可能太大,在大屏幕上按钮可能太小。用百分比可以自适应,在任何屏幕上都能保持合适的比例。

按钮样式

  • 深蓝色背景 #1a1a3e,和卡片背景一致
  • 圆角 12,比卡片的圆角 16 小,因为按钮比卡片小
  • 内边距 16,让按钮有足够的点击区域。按钮太小不好点,太大又浪费空间,16 像素是一个平衡点
  • flexDirection: 'row' 让图标和文字横向排列
  • justifyContent: 'center' 让它们水平居中
  • alignItems: 'center' 让它们垂直居中

按钮内容

  • 图标 18 号字体,文字 13 号字体,图标稍大一点更醒目
  • 文字用中等粗细(fontWeight: '600'),比正常文字粗一点,但不像标题那么粗
  • marginRight: 8 在图标和文字之间留出间距

边框颜色和其他卡片一样,保持视觉一致性。所有卡片、按钮、输入框都用相同的配色方案(背景 #1a1a3e,边框 #3a3a6a),让界面看起来很统一。

小结

这个去除空格工具展示了文本处理的常见模式。6 种处理方式通过正则表达式和字符串方法实现,从简单的 trim() 到复杂的"每行去首尾",满足不同需求。实时统计功能让用户了解文本中空格、制表符、换行符的数量。按钮和统计区的动画让工具用起来更流畅。在 OpenHarmony 平台上,正则表达式和字符串方法是 JavaScript 标准 API,跨平台通用。


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

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐