今天我们用 React Native 实现一个百分比计算工具,支持 5 种常见的百分比计算场景。
请添加图片描述

状态设计

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

export const PercentCalculator: React.FC = () => {
  const [mode, setMode] = useState(0);
  const [val1, setVal1] = useState('');
  const [val2, setVal2] = useState('');
  const [result, setResult] = useState('');
  
  const buttonAnim = useRef(new Animated.Value(1)).current;
  const resultAnim = useRef(new Animated.Value(0)).current;
  const modeAnims = useRef(Array(5).fill(0).map(() => new Animated.Value(1))).current;

状态设计包含模式、两个输入值、结果、动画值。

模式mode 是数字,表示当前选择的计算模式(0-4)。

两个输入值val1val2 都是字符串类型,存储用户输入的数字。

结果result 是字符串类型,存储计算结果。

三个动画值

  • buttonAnim:按钮的缩放动画
  • resultAnim:结果卡片的缩放和透明度动画
  • modeAnims:模式按钮的动画数组,5 个元素对应 5 种模式

为什么模式用数字?因为模式是固定的 5 种,用数字索引最简单。0 表示第一种模式,1 表示第二种,依次类推。

为什么结果用字符串?因为结果可能包含百分号(比如"25%"),用字符串存储更灵活。

模式配置

  const modes = [
    { title: 'X 的 Y% 是多少?', icon: '📊', labels: ['X', 'Y%'], calc: (a: number, b: number) => (a * b / 100).toFixed(2) },
    { title: 'X 是 Y 的百分之几?', icon: '📈', labels: ['X', 'Y'], calc: (a: number, b: number) => ((a / b) * 100).toFixed(2) + '%' },
    { title: 'X 增加 Y% 后是多少?', icon: '⬆️', labels: ['X', 'Y%'], calc: (a: number, b: number) => (a * (1 + b / 100)).toFixed(2) },
    { title: 'X 减少 Y% 后是多少?', icon: '⬇️', labels: ['X', 'Y%'], calc: (a: number, b: number) => (a * (1 - b / 100)).toFixed(2) },
    { title: '从 X 到 Y 变化了百分之几?', icon: '🔄', labels: ['X', 'Y'], calc: (a: number, b: number) => (((b - a) / a) * 100).toFixed(2) + '%' },
  ];

模式配置数组,每个模式包含标题、图标、标签、计算函数。

5 种模式

  1. X 的 Y% 是多少?比如 100 的 20% 是 20
  2. X 是 Y 的百分之几?比如 20 是 100 的 20%
  3. X 增加 Y% 后是多少?比如 100 增加 20% 后是 120
  4. X 减少 Y% 后是多少?比如 100 减少 20% 后是 80
  5. 从 X 到 Y 变化了百分之几?比如从 100 到 120 变化了 20%

每个模式的属性

  • title:标题,描述计算场景
  • icon:图标,视觉标识
  • labels:输入框标签,比如 [‘X’, ‘Y%’]
  • calc:计算函数,接收两个数字,返回结果字符串

为什么用配置数组?因为 5 种模式的结构相同,只是标题、图标、计算公式不同。用配置数组可以避免重复代码,添加新模式也很方便。

计算函数解析

  1. (a * b / 100).toFixed(2):a 乘以 b%,保留 2 位小数
  2. ((a / b) * 100).toFixed(2) + '%':a 除以 b 再乘以 100,加上百分号
  3. (a * (1 + b / 100)).toFixed(2):a 乘以 (1 + b%)
  4. (a * (1 - b / 100)).toFixed(2):a 乘以 (1 - b%)
  5. (((b - a) / a) * 100).toFixed(2) + '%':(b - a) 除以 a 再乘以 100,加上百分号

为什么用 toFixed(2)?因为百分比计算经常产生小数,保留 2 位小数让结果更易读。比如 100 的 33% 是 33.00,而不是 33.333333…。

模式切换

  const handleModeChange = (i: number) => {
    Animated.sequence([
      Animated.timing(modeAnims[i], { toValue: 0.95, duration: 100, useNativeDriver: true }),
      Animated.spring(modeAnims[i], { toValue: 1, friction: 3, useNativeDriver: true }),
    ]).start();
    setMode(i);
    setResult('');
  };

模式切换函数触发动画,设置模式,清空结果。

按钮动画:序列动画,先缩小到 95%(100ms),再弹回到 100%。营造"按下"的感觉。

设置模式setMode(i) 设置当前模式为 i。

清空结果setResult('') 清空结果,因为切换模式后,旧的结果不再有效。

为什么要清空结果?因为不同模式的计算公式不同,旧的结果对新模式没有意义。比如用户在模式 1 计算了"100 的 20% 是 20",切换到模式 2 后,这个结果就不对了。清空结果避免混淆。

计算函数

  const calculate = () => {
    Animated.sequence([
      Animated.timing(buttonAnim, { toValue: 0.9, duration: 100, useNativeDriver: true }),
      Animated.spring(buttonAnim, { toValue: 1, friction: 3, useNativeDriver: true }),
    ]).start();
    
    const a = parseFloat(val1);
    const b = parseFloat(val2);
    if (isNaN(a) || isNaN(b)) return;
    
    resultAnim.setValue(0);
    Animated.spring(resultAnim, { toValue: 1, friction: 4, useNativeDriver: true }).start();
    
    setResult(modes[mode].calc(a, b));
  };

计算按钮点击时,触发动画,验证输入,调用计算函数。

按钮动画:序列动画,先缩小到 90%(100ms),再弹回到 100%。营造"按下"的感觉。

解析数字parseFloat() 把字符串转成浮点数。

验证输入:如果 ab 不是数字(NaN),直接返回,不执行计算。

结果动画:重置动画值为 0,然后弹簧动画到 1。结果卡片从小到大弹出。

调用计算函数modes[mode].calc(a, b) 调用当前模式的计算函数,传入两个数字,返回结果字符串。

为什么用 parseFloat 而不是 parseInt?因为百分比计算可能涉及小数。比如"100 的 33.5% 是多少",如果用 parseInt,33.5 会变成 33,结果错误。用 parseFloat 可以正确处理小数。

界面渲染:头部和模式选择

  return (
    <ScrollView style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.headerIcon}>📊</Text>
        <Text style={styles.headerTitle}>百分比计算</Text>
      </View>

      <View style={styles.modeSelector}>
        {modes.map((m, i) => (
          <Animated.View key={i} style={{ transform: [{ scale: modeAnims[i] }] }}>
            <TouchableOpacity style={[styles.modeBtn, mode === i && styles.modeBtnActive]} onPress={() => handleModeChange(i)} activeOpacity={0.7}>
              <Text style={styles.modeIcon}>{m.icon}</Text>
              <Text style={[styles.modeText, mode === i && styles.modeTextActive]}>{m.title}</Text>
            </TouchableOpacity>
          </Animated.View>
        ))}
      </View>

头部显示标题,模式选择器显示 5 种模式。

头部

  • 图标:📊 柱状图
  • 标题:百分比计算

模式选择器

  • 遍历 modes 数组,生成 5 个按钮
  • 每个按钮应用缩放动画
  • 当前模式的按钮用蓝色背景(modeBtnActive
  • 按钮包含图标和标题

为什么用不同图标?因为图标能快速传达模式的含义。📊 表示"计算百分比",📈 表示"比例",⬆️ 表示"增加",⬇️ 表示"减少",🔄 表示"变化"。用户看到图标就知道是什么模式。

输入区域

      <View style={styles.inputSection}>
        <View style={styles.inputGroup}>
          <Text style={styles.label}>{modes[mode].labels[0]}</Text>
          <View style={styles.inputWrapper}>
            <TextInput style={styles.input} value={val1} onChangeText={setVal1} keyboardType="numeric" placeholderTextColor="#666" />
          </View>
        </View>
        <View style={styles.inputGroup}>
          <Text style={styles.label}>{modes[mode].labels[1]}</Text>
          <View style={styles.inputWrapper}>
            <TextInput style={styles.input} value={val2} onChangeText={setVal2} keyboardType="numeric" placeholderTextColor="#666" />
          </View>
        </View>
      </View>

输入区域包含两个输入组,每个输入组有标签和输入框。

横向布局:两个输入组并排,各占 50% 宽度。

标签:根据当前模式显示不同的标签。比如模式 0 的标签是 [‘X’, ‘Y%’],模式 1 的标签是 [‘X’, ‘Y’]。

输入框

  • keyboardType="numeric":弹出数字键盘
  • textAlign: 'center':居中对齐

为什么标签根据模式变化?因为不同模式的输入含义不同。模式 0 是"X 的 Y% 是多少",标签是 [‘X’, ‘Y%’]。模式 1 是"X 是 Y 的百分之几",标签是 [‘X’, ‘Y’]。动态标签让用户清楚"应该输入什么"。

按钮和结果

      <Animated.View style={{ transform: [{ scale: buttonAnim }] }}>
        <TouchableOpacity style={styles.btn} onPress={calculate} activeOpacity={0.8}>
          <Text style={styles.btnText}>🧮 计算</Text>
        </TouchableOpacity>
      </Animated.View>

      {result && (
        <Animated.View style={[styles.result, { transform: [{ scale: resultAnim }], opacity: resultAnim }]}>
          <Text style={styles.resultLabel}>结果</Text>
          <Text style={styles.resultValue}>{result}</Text>
        </Animated.View>
      )}
    </ScrollView>
  );
};

按钮触发计算,结果卡片显示计算结果。

按钮

  • 应用缩放动画,点击时缩小再弹回
  • 图标:🧮 算盘
  • 文字:计算

结果卡片

  • 条件渲染:只有 result 不为空时才显示
  • 动画:缩放和透明度从 0 到 1
  • 标签:灰色小字"结果"
  • 数值:蓝色大字,字号 48,加粗
  • 蓝色边框

为什么结果字号这么大?因为结果是用户最关心的信息,大字号让结果更醒目。用户计算完成后,第一眼就能看到结果。

鸿蒙 ArkTS 对比:百分比计算

@State mode: number = 0
@State val1: string = ''
@State val2: string = ''
@State result: string = ''

modes = [
  { title: 'X 的 Y% 是多少?', icon: '📊', labels: ['X', 'Y%'], 
    calc: (a: number, b: number) => (a * b / 100).toFixed(2) },
  { title: 'X 是 Y 的百分之几?', icon: '📈', labels: ['X', 'Y'], 
    calc: (a: number, b: number) => ((a / b) * 100).toFixed(2) + '%' },
  // ... 其他模式
]

calculate() {
  const a = parseFloat(this.val1)
  const b = parseFloat(this.val2)
  if (isNaN(a) || isNaN(b)) return
  
  this.result = this.modes[this.mode].calc(a, b)
}

build() {
  Column() {
    Text('百分比计算')
      .fontSize(28)
      .fontWeight(FontWeight.Bold)
    
    ForEach(this.modes, (m: any, i: number) => {
      Button() {
        Row() {
          Text(m.icon).fontSize(20)
          Text(m.title).fontSize(14)
        }
      }
      .backgroundColor(this.mode === i ? '#4A90D9' : '#1a1a3e')
      .onClick(() => {
        this.mode = i
        this.result = ''
      })
    })
    
    Row() {
      Column() {
        Text(this.modes[this.mode].labels[0])
        TextInput({ text: this.val1 })
          .type(InputType.Number)
          .onChange((value: string) => {
            this.val1 = value
          })
      }
      
      Column() {
        Text(this.modes[this.mode].labels[1])
        TextInput({ text: this.val2 })
          .type(InputType.Number)
          .onChange((value: string) => {
            this.val2 = value
          })
      }
    }
    
    Button('🧮 计算')
      .onClick(() => {
        this.calculate()
      })
    
    if (this.result) {
      Column() {
        Text('结果')
          .fontSize(14)
          .fontColor('#888')
        Text(this.result)
          .fontSize(48)
          .fontWeight(FontWeight.Bold)
          .fontColor('#4A90D9')
      }
    }
  }
}

ArkTS 中的百分比计算逻辑完全一样。核心是配置数组 + 计算函数。parseFloat()isNaN()toFixed() 都是标准 JavaScript API,跨平台通用。

为什么算法跨平台通用?因为百分比计算是纯数学运算,不涉及 UI、动画、平台 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' },
  modeSelector: { marginBottom: 20 },
  modeBtn: { backgroundColor: '#1a1a3e', padding: 14, borderRadius: 12, marginBottom: 8, flexDirection: 'row', alignItems: 'center', borderWidth: 1, borderColor: '#3a3a6a' },
  modeBtnActive: { backgroundColor: '#4A90D9', borderColor: '#4A90D9' },
  modeIcon: { fontSize: 20, marginRight: 12 },
  modeText: { color: '#888', flex: 1 },
  modeTextActive: { color: '#fff', fontWeight: '600' },
  inputSection: { flexDirection: 'row', marginBottom: 20 },
  inputGroup: { flex: 1, marginHorizontal: 4 },
  label: { fontSize: 14, color: '#888', marginBottom: 8 },
  inputWrapper: { backgroundColor: '#1a1a3e', borderRadius: 12, borderWidth: 1, borderColor: '#3a3a6a' },
  input: { padding: 16, fontSize: 20, color: '#fff', textAlign: 'center' },
  btn: { backgroundColor: '#4A90D9', padding: 18, borderRadius: 16, alignItems: 'center', marginBottom: 24 },
  btnText: { color: '#fff', fontSize: 18, fontWeight: '700' },
  result: { backgroundColor: '#1a1a3e', padding: 30, borderRadius: 20, alignItems: 'center', borderWidth: 1, borderColor: '#4A90D9' },
  resultLabel: { color: '#888', marginBottom: 12, fontSize: 14 },
  resultValue: { fontSize: 48, fontWeight: '700', color: '#4A90D9' },
});

容器用深蓝黑色背景。模式按钮横向布局,图标在左,标题在右。激活的按钮用蓝色背景和白色文字。输入区域横向布局,两个输入组各占 50%。结果卡片居中对齐,蓝色边框,结果字号 48。

小结

这个百分比计算工具展示了配置驱动的实现。用配置数组定义 5 种模式,每个模式包含标题、图标、标签、计算函数。动态标签根据模式变化,让用户清楚输入含义。


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

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐