今天我们用 React Native 实现一个折扣计算工具,支持快速选择折扣,实时显示节省金额和折后价。
请添加图片描述

状态设计

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

export const DiscountCalculator: React.FC = () => {
  const [price, setPrice] = useState('');
  const [discount, setDiscount] = useState('');
  
  const resultAnim = useRef(new Animated.Value(1)).current;
  const savedAnim = useRef(new Animated.Value(1)).current;
  const pulseAnim = useRef(new Animated.Value(1)).current;

状态设计包含原价、折扣、动画值。

原价price 是字符串类型,存储用户输入的原价。

折扣discount 是字符串类型,存储用户输入的折扣百分比。

三个动画值

  • resultAnim:结果卡片的缩放动画
  • savedAnim:节省金额的缩放动画
  • pulseAnim:折后价的脉冲动画(循环放大缩小)

为什么折扣用百分比?因为折扣通常用百分比表示,比如"打 8 折"就是"20% off"。用百分比更符合用户习惯。

脉冲动画

  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();
  }, []);

组件挂载时,启动折后价的脉冲动画。

循环动画Animated.loop() 让动画无限循环。

序列动画

  1. 放大到 105%(1 秒)
  2. 缩回到 100%(1 秒)

为什么用脉冲动画?因为折后价是用户最关心的信息,脉冲动画让它更醒目。折后价不断放大缩小,像"心跳"一样,吸引用户注意力。

输入变化动画

  useEffect(() => {
    Animated.sequence([
      Animated.timing(resultAnim, { toValue: 0.95, duration: 100, useNativeDriver: true }),
      Animated.spring(resultAnim, { toValue: 1, friction: 4, useNativeDriver: true }),
    ]).start();
  }, [price, discount]);

监听原价和折扣变化,触发结果卡片的动画。

依赖数组[price, discount] 表示当原价或折扣变化时,执行动画。

序列动画

  1. 缩小到 95%(100ms)
  2. 弹回到 100%(弹簧动画)

为什么监听输入变化?因为用户每次修改原价或折扣,结果都会变化。动画让用户感知到"结果更新了",增加交互反馈。

计算函数

  const calculate = () => {
    const p = parseFloat(price) || 0;
    const d = parseFloat(discount) || 0;
    const saved = p * d / 100;
    const final = p - saved;
    return { saved: saved.toFixed(2), final: final.toFixed(2) };
  };

计算节省金额和折后价。

解析输入

  • parseFloat(price) || 0:把原价转成浮点数,如果失败(NaN)则默认 0
  • parseFloat(discount) || 0:把折扣转成浮点数,如果失败则默认 0

计算节省金额saved = p * d / 100。比如原价 100,折扣 20%,节省 100 × 20 / 100 = 20。

计算折后价final = p - saved。比如原价 100,节省 20,折后价 100 - 20 = 80。

保留 2 位小数toFixed(2) 让结果更易读,比如 19.99 而不是 19.989999…。

为什么用 || 0 而不是验证输入?因为折扣计算是实时的,用户输入过程中可能出现空字符串或无效输入。用 || 0 让计算不会报错,显示 0 而不是 NaN。

快速选择折扣

  const handleDiscountSelect = (d: number) => {
    Animated.sequence([
      Animated.timing(savedAnim, { toValue: 1.2, duration: 100, useNativeDriver: true }),
      Animated.spring(savedAnim, { toValue: 1, friction: 3, useNativeDriver: true }),
    ]).start();
    setDiscount(String(d));
  };

  const result = calculate();
  const quickDiscounts = [10, 20, 30, 50, 70, 80];

快速选择折扣函数触发动画,设置折扣。

节省金额动画:序列动画,先放大到 120%(100ms),再弹回到 100%。强调"节省金额变化了"。

设置折扣setDiscount(String(d)) 把数字转成字符串,设置到状态中。

快速折扣数组[10, 20, 30, 50, 70, 80] 是常见的折扣百分比。

为什么提供快速选择?因为折扣通常是固定的几个值,比如"打 8 折"(20% off)、“打 5 折”(50% off)。快速选择让用户不需要手动输入,点击按钮就能选择。

界面渲染:头部和原价输入

  return (
    <ScrollView style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.headerIcon}>🏷️</Text>
        <Text style={styles.headerTitle}>折扣计算</Text>
      </View>

      <View style={styles.inputCard}>
        <Text style={styles.label}>💰 原价</Text>
        <View style={styles.inputWrapper}>
          <TextInput style={styles.input} value={price} onChangeText={setPrice} keyboardType="numeric" placeholder="0.00" placeholderTextColor="#666" />
        </View>
      </View>

头部显示标题,原价输入卡片包含标签和输入框。

头部

  • 图标:🏷️ 标签(折扣标签)
  • 标题:折扣计算

原价输入卡片

  • 标签:💰 原价
  • 输入框:keyboardType="numeric" 弹出数字键盘,textAlign: 'center' 居中对齐,字号 28

为什么输入框字号这么大?因为价格是重要信息,大字号让用户看得更清楚。输入价格时,用户需要确认"输入的数字对不对",大字号减少输入错误。

折扣输入和快速选择

      <View style={styles.inputCard}>
        <Text style={styles.label}>📊 折扣 (%)</Text>
        <View style={styles.inputWrapper}>
          <TextInput style={styles.input} value={discount} onChangeText={setDiscount} keyboardType="numeric" placeholder="0" placeholderTextColor="#666" />
        </View>
        <View style={styles.quickBtns}>
          {quickDiscounts.map(d => (
            <TouchableOpacity key={d} style={[styles.quickBtn, discount === String(d) && styles.quickBtnActive]} onPress={() => handleDiscountSelect(d)} activeOpacity={0.7}>
              <Text style={[styles.quickText, discount === String(d) && styles.quickTextActive]}>{d}%</Text>
            </TouchableOpacity>
          ))}
        </View>
      </View>

折扣输入卡片包含标签、输入框、快速选择按钮。

标签:📊 折扣 (%)

输入框:同原价输入框,字号 28,居中对齐。

快速选择按钮

  • 遍历 quickDiscounts 数组,生成 6 个按钮
  • 当前选中的按钮用蓝色背景(quickBtnActive
  • 按钮文字显示折扣百分比,比如"10%"

为什么快速选择按钮在输入框下面?因为快速选择是"辅助功能",不是主要功能。用户可以手动输入折扣,也可以点击按钮快速选择。把按钮放在输入框下面,不会干扰输入,但又很容易点击。

结果显示

      <Animated.View style={[styles.result, { transform: [{ scale: resultAnim }] }]}>
        <View style={styles.resultRow}>
          <Text style={styles.resultLabel}>💸 节省</Text>
          <Animated.Text style={[styles.resultSaved, { transform: [{ scale: savedAnim }] }]}>¥ {result.saved}</Animated.Text>
        </View>
        <Animated.View style={[styles.resultRowMain, { transform: [{ scale: pulseAnim }] }]}>
          <Text style={styles.resultLabelMain}>🎉 折后价</Text>
          <Text style={styles.resultFinal}>¥ {result.final}</Text>
        </Animated.View>
      </Animated.View>

结果卡片显示节省金额和折后价。

结果卡片动画:应用缩放动画,输入变化时缩小再弹回。

节省金额行

  • 标签:💸 节省
  • 金额:绿色文字,字号 24,应用缩放动画

折后价行

  • 标签:🎉 折后价
  • 金额:红色文字,字号 48,应用脉冲动画

为什么节省金额用绿色,折后价用红色?因为绿色代表"省钱",红色代表"价格"。绿色让用户感觉"省了钱,很开心",红色让用户关注"最终要付多少钱"。

为什么折后价字号更大?因为折后价是用户最关心的信息。用户购物时,最想知道的是"最终要付多少钱"。大字号 + 红色 + 脉冲动画,让折后价成为视觉焦点。

折扣换算提示

      <View style={styles.tips}>
        <Text style={styles.tipsTitle}>💡 折扣换算</Text>
        <View style={styles.tipsList}>
          {[
            { zh: '打9折', en: '10% off' },
            { zh: '打8折', en: '20% off' },
            { zh: '打7折', en: '30% off' },
            { zh: '打5折', en: '50% off' },
          ].map((item, i) => (
            <View key={i} style={styles.tipItem}>
              <Text style={styles.tipZh}>{item.zh}</Text>
              <Text style={styles.tipEn}>{item.en}</Text>
            </View>
          ))}
        </View>
      </View>
    </ScrollView>
  );
};

折扣换算提示显示中文折扣和英文折扣的对应关系。

标题:💡 折扣换算

提示列表

  • 打 9 折 = 10% off
  • 打 8 折 = 20% off
  • 打 7 折 = 30% off
  • 打 5 折 = 50% off

为什么显示折扣换算?因为中文和英文的折扣表示方式不同。中文说"打 8 折",意思是"付 80%“,折扣是 20%。英文说"20% off”,意思是"减 20%",折扣也是 20%。很多人搞不清楚,显示换算表帮助用户理解。

为什么只显示 4 个常见折扣?因为这 4 个是最常见的折扣。打 9 折(小折扣)、打 8 折(常见折扣)、打 7 折(较大折扣)、打 5 折(半价)。显示太多会让界面拥挤,4 个刚好。

鸿蒙 ArkTS 对比:折扣计算

@State price: string = ''
@State discount: string = ''

calculate() {
  const p = parseFloat(this.price) || 0
  const d = parseFloat(this.discount) || 0
  const saved = p * d / 100
  const final = p - saved
  return { saved: saved.toFixed(2), final: final.toFixed(2) }
}

build() {
  Column() {
    Text('折扣计算')
      .fontSize(28)
      .fontWeight(FontWeight.Bold)
    
    Column() {
      Text('💰 原价')
        .fontSize(14)
        .fontColor('#888')
      TextInput({ text: this.price, placeholder: '0.00' })
        .type(InputType.Number)
        .fontSize(28)
        .textAlign(TextAlign.Center)
        .onChange((value: string) => {
          this.price = value
        })
    }
    
    Column() {
      Text('📊 折扣 (%)')
        .fontSize(14)
        .fontColor('#888')
      TextInput({ text: this.discount, placeholder: '0' })
        .type(InputType.Number)
        .fontSize(28)
        .textAlign(TextAlign.Center)
        .onChange((value: string) => {
          this.discount = value
        })
      
      Row() {
        ForEach([10, 20, 30, 50, 70, 80], (d: number) => {
          Button(`${d}%`)
            .backgroundColor(this.discount === String(d) ? '#4A90D9' : '#252550')
            .onClick(() => {
              this.discount = String(d)
            })
        })
      }
    }
    
    Column() {
      Row() {
        Text('💸 节省')
        Text(`¥ ${this.calculate().saved}`)
          .fontSize(24)
          .fontColor('#2ecc71')
          .fontWeight(FontWeight.Bold)
      }
      
      Column() {
        Text('🎉 折后价')
        Text(`¥ ${this.calculate().final}`)
          .fontSize(48)
          .fontColor('#e74c3c')
          .fontWeight(FontWeight.Bold)
      }
    }
  }
}

ArkTS 中的折扣计算逻辑完全一样。核心是公式:节省 = 原价 × 折扣 / 100,折后价 = 原价 - 节省。parseFloat()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' },
  inputCard: { backgroundColor: '#1a1a3e', borderRadius: 16, padding: 16, marginBottom: 16, borderWidth: 1, borderColor: '#3a3a6a' },
  label: { fontSize: 14, color: '#888', marginBottom: 12 },
  inputWrapper: { backgroundColor: '#252550', borderRadius: 12 },
  input: { padding: 16, fontSize: 28, textAlign: 'center', color: '#fff' },
  quickBtns: { flexDirection: 'row', flexWrap: 'wrap', marginTop: 12 },
  quickBtn: { paddingVertical: 10, paddingHorizontal: 16, backgroundColor: '#252550', borderRadius: 10, marginRight: 8, marginBottom: 8 },
  quickBtnActive: { backgroundColor: '#4A90D9' },
  quickText: { color: '#888', fontWeight: '600' },
  quickTextActive: { color: '#fff' },
  result: { backgroundColor: '#1a1a3e', padding: 20, borderRadius: 20, marginBottom: 20, borderWidth: 1, borderColor: '#3a3a6a' },
  resultRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 14, borderBottomWidth: 1, borderBottomColor: '#3a3a6a' },
  resultLabel: { fontSize: 16, color: '#888' },
  resultSaved: { fontSize: 24, color: '#2ecc71', fontWeight: '700' },
  resultRowMain: { paddingTop: 20, alignItems: 'center' },
  resultLabelMain: { fontSize: 14, color: '#888', marginBottom: 8 },
  resultFinal: { fontSize: 48, color: '#e74c3c', fontWeight: '700' },
  tips: { backgroundColor: '#1a1a3e', padding: 16, borderRadius: 16, borderWidth: 1, borderColor: '#3a3a6a' },
  tipsTitle: { fontSize: 16, fontWeight: '600', marginBottom: 16, color: '#fff' },
  tipsList: {},
  tipItem: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: '#3a3a6a' },
  tipZh: { color: '#fff', fontSize: 14 },
  tipEn: { color: '#4A90D9', fontSize: 14 },
});

容器用深蓝黑色背景。输入卡片用深蓝色背景,圆角 16。输入框字号 28,居中对齐。快速选择按钮用网格布局,自动换行。节省金额绿色,字号 24。折后价红色,字号 48。

小结

这个折扣计算工具展示了实时计算和动画反馈的实现。用公式计算节省金额和折后价,监听输入变化触发动画。快速选择按钮让用户不需要手动输入折扣。脉冲动画让折后价更醒目。


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

Logo

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

更多推荐