React Native for OpenHarmony 实战:星座查询实现
本文介绍了一个使用React Native实现的星座查询工具。该应用包含12个星座数据(名称、符号、日期范围、元素和性格特征),用户可通过输入生日或直接选择星座来查询。核心技术包括:1)基于日期范围的星座计算算法;2)多种动画效果(缩放、旋转、网格延迟);3)根据元素类型(火、土、风、水)显示不同颜色。实现细节涵盖状态管理、动画初始化与触发逻辑,以及日期分界点的特殊处理方式。该工具既可作为娱乐应用
今天我们用 React Native 实现一个星座查询工具,根据生日查询对应的星座和性格特征。
星座数据
import React, { useState, useRef, useEffect } from 'react';
import { View, Text, TextInput, TouchableOpacity, StyleSheet, ScrollView, Animated } from 'react-native';
const constellations = [
{ name: '白羊座', emoji: '♈', dates: '3/21-4/19', element: '火', traits: '热情、冲动、勇敢' },
{ name: '金牛座', emoji: '♉', dates: '4/20-5/20', element: '土', traits: '稳重、务实、固执' },
{ name: '双子座', emoji: '♊', dates: '5/21-6/21', element: '风', traits: '机智、善变、好奇' },
{ name: '巨蟹座', emoji: '♋', dates: '6/22-7/22', element: '水', traits: '敏感、顾家、情绪化' },
{ name: '狮子座', emoji: '♌', dates: '7/23-8/22', element: '火', traits: '自信、慷慨、爱面子' },
{ name: '处女座', emoji: '♍', dates: '8/23-9/22', element: '土', traits: '完美主义、细心、挑剔' },
{ name: '天秤座', emoji: '♎', dates: '9/23-10/23', element: '风', traits: '优雅、公正、犹豫' },
{ name: '天蝎座', emoji: '♏', dates: '10/24-11/22', element: '水', traits: '神秘、专注、记仇' },
{ name: '射手座', emoji: '♐', dates: '11/23-12/21', element: '火', traits: '乐观、自由、直率' },
{ name: '摩羯座', emoji: '♑', dates: '12/22-1/19', element: '土', traits: '务实、有野心、保守' },
{ name: '水瓶座', emoji: '♒', dates: '1/20-2/18', element: '风', traits: '独立、创新、叛逆' },
{ name: '双鱼座', emoji: '♓', dates: '2/19-3/20', element: '水', traits: '浪漫、敏感、逃避' },
];
星座数据数组,包含 12 个星座的信息。
每个星座的属性:
name:星座名称emoji:星座符号dates:日期范围element:元素(火、土、风、水)traits:性格特征
为什么用元素分类?因为西方占星学把 12 星座分成 4 个元素:火象(白羊、狮子、射手)、土象(金牛、处女、摩羯)、风象(双子、天秤、水瓶)、水象(巨蟹、天蝎、双鱼)。每个元素有不同的性格特点。
状态设计
export const Constellation: React.FC = () => {
const [month, setMonth] = useState('');
const [day, setDay] = useState('');
const [result, setResult] = useState<typeof constellations[0] | null>(null);
const scaleAnim = useRef(new Animated.Value(0)).current;
const rotateAnim = useRef(new Animated.Value(0)).current;
const gridAnims = useRef(constellations.map(() => new Animated.Value(0))).current;
状态设计包含月份、日期、查询结果、动画值。
月份和日期:month 和 day 都是字符串类型,存储用户输入的月份和日期。
查询结果:result 是星座对象或 null。
三个动画值:
scaleAnim:结果卡片的缩放动画rotateAnim:星座符号的旋转动画gridAnims:星座网格的动画数组,12 个元素
网格动画初始化
useEffect(() => {
gridAnims.forEach((anim, index) => {
Animated.timing(anim, { toValue: 1, duration: 300, delay: index * 40, useNativeDriver: true }).start();
});
}, []);
组件挂载时,初始化星座网格的动画。每个星座延迟 index * 40 毫秒启动,持续 300ms,依次出现。
星座计算
const getConstellation = (m: number, d: number) => {
const dates = [[20, 19], [19, 20], [21, 20], [20, 21], [21, 22], [22, 23], [23, 23], [23, 23], [23, 22], [24, 22], [23, 21], [22, 20]];
const index = d < dates[m - 1][0] ? (m + 10) % 12 : (m + 11) % 12;
return constellations[index];
};
根据月份和日期计算星座。
日期分界点数组:dates 是一个二维数组,每个元素是 [前一个星座的结束日, 当前星座的结束日]。
计算索引:
- 如果日期小于分界点,属于前一个星座:
(m + 10) % 12 - 否则属于当前星座:
(m + 11) % 12
为什么用这个公式?因为星座是按月份顺序排列的,但每个星座的起始日期不同。用分界点判断,再用取余运算处理跨年的情况(12 月到 1 月)。
举例:计算 3 月 25 日的星座。
- m = 3, d = 25
- dates[2] = [21, 20](3 月的分界点是 21 日)
- d (25) >= dates[2][0] (21),属于当前星座
- index = (3 + 11) % 12 = 2
- constellations[2] = { name: ‘双子座’, … }
等等,这个例子有问题。让我重新理解:3 月 25 日应该是白羊座(3/21-4/19)。
实际上,dates 数组的含义是:
- dates[0] = [20, 19]:1 月 20 日是水瓶座的开始,1 月 19 日是摩羯座的结束
- dates[1] = [19, 20]:2 月 19 日是双鱼座的开始
- dates[2] = [21, 20]:3 月 21 日是白羊座的开始
如果 d < dates[m-1][0],说明还在前一个星座。
查询函数
const search = () => {
const m = parseInt(month);
const d = parseInt(day);
if (m >= 1 && m <= 12 && d >= 1 && d <= 31) {
scaleAnim.setValue(0);
rotateAnim.setValue(0);
setResult(getConstellation(m, d));
Animated.parallel([
Animated.spring(scaleAnim, { toValue: 1, friction: 4, useNativeDriver: true }),
Animated.timing(rotateAnim, { toValue: 1, duration: 500, useNativeDriver: true }),
]).start();
}
};
查询按钮点击时,计算星座,触发动画。
解析输入:parseInt() 把字符串转成整数。
验证输入:月份 1-12,日期 1-31。
重置动画值:把缩放和旋转动画值都设为 0。
设置结果:调用 getConstellation(m, d) 计算星座。
并行动画:缩放动画(弹簧)和旋转动画(500ms)同时触发。
选择星座
const selectConstellation = (c: typeof constellations[0]) => {
scaleAnim.setValue(0);
rotateAnim.setValue(0);
setResult(c);
Animated.parallel([
Animated.spring(scaleAnim, { toValue: 1, friction: 4, useNativeDriver: true }),
Animated.timing(rotateAnim, { toValue: 1, duration: 500, useNativeDriver: true }),
]).start();
};
const spin = rotateAnim.interpolate({ inputRange: [0, 1], outputRange: ['0deg', '360deg'] });
点击星座网格时,直接显示该星座的信息。逻辑同查询函数。
旋转插值:把动画值 0-1 映射到旋转角度 0-360 度。
元素颜色
const getElementColor = (element: string) => {
switch (element) {
case '火': return '#e74c3c';
case '土': return '#8b4513';
case '风': return '#3498db';
case '水': return '#1abc9c';
default: return '#4A90D9';
}
};
根据元素返回对应的颜色。
四种颜色:
- 火:红色
#e74c3c - 土:棕色
#8b4513 - 风:蓝色
#3498db - 水:青色
#1abc9c
为什么用不同颜色?因为颜色能直观地表达元素的特性。火是红色(热情),土是棕色(稳重),风是蓝色(自由),水是青色(流动)。
界面渲染:头部和输入
return (
<ScrollView style={styles.container}>
<View style={styles.header}>
<Text style={styles.headerEmoji}>⭐</Text>
<Text style={styles.headerTitle}>星座查询</Text>
<Text style={styles.headerSubtitle}>探索你的星座</Text>
</View>
<View style={styles.inputSection}>
<View style={styles.inputGroup}>
<Text style={styles.label}>月</Text>
<TextInput style={styles.input} value={month} onChangeText={setMonth} keyboardType="numeric" placeholder="1-12" placeholderTextColor="#666" />
</View>
<View style={styles.inputGroup}>
<Text style={styles.label}>日</Text>
<TextInput style={styles.input} value={day} onChangeText={setDay} keyboardType="numeric" placeholder="1-31" placeholderTextColor="#666" />
</View>
<TouchableOpacity style={styles.btn} onPress={search} activeOpacity={0.8}>
<Text style={styles.btnText}>查询</Text>
</TouchableOpacity>
</View>
头部显示标题和副标题,输入区域包含月份、日期输入框和查询按钮。
头部:
- 图标:⭐ 星星
- 标题:星座查询
- 副标题:探索你的星座
输入区域:
- 横向布局,底部对齐
- 月份输入框:标签"月",占位符"1-12"
- 日期输入框:标签"日",占位符"1-31"
- 查询按钮:紫色背景,带阴影
为什么用紫色按钮?因为紫色代表神秘、浪漫,符合星座查询的主题。
结果显示
{result && (
<Animated.View style={[styles.result, { transform: [{ scale: scaleAnim }], borderColor: getElementColor(result.element) }]}>
<Animated.Text style={[styles.emoji, { transform: [{ rotate: spin }] }]}>{result.emoji}</Animated.Text>
<Text style={styles.name}>{result.name}</Text>
<Text style={styles.dates}>{result.dates}</Text>
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>元素</Text>
<Text style={[styles.infoValue, { color: getElementColor(result.element) }]}>{result.element}象星座</Text>
</View>
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>特点</Text>
<Text style={styles.infoValue}>{result.traits}</Text>
</View>
</Animated.View>
)}
结果卡片显示星座信息。
条件渲染:只有 result 不为 null 时才显示。
卡片动画:缩放从 0 到 1。
边框颜色:根据元素动态设置,火象红色,土象棕色,风象蓝色,水象青色。
星座符号:
- 字号 60
- 应用旋转动画,旋转一圈
星座名称:白色文字,字号 28,加粗。
日期范围:灰色文字。
信息行:
- 元素:标签"元素",值"X象星座",颜色根据元素动态设置
- 特点:标签"特点",值是性格特征
为什么边框颜色根据元素变化?因为颜色能快速传达元素信息。用户看到红色边框,就知道是火象星座。看到蓝色边框,就知道是风象星座。
星座网格
<View style={styles.grid}>
{constellations.map((c, i) => (
<Animated.View key={i} style={{ opacity: gridAnims[i], transform: [{ scale: gridAnims[i] }] }}>
<TouchableOpacity style={[styles.gridItem, result?.name === c.name && styles.gridItemActive]} onPress={() => selectConstellation(c)} activeOpacity={0.7}>
<Text style={styles.gridEmoji}>{c.emoji}</Text>
<Text style={[styles.gridName, result?.name === c.name && styles.gridNameActive]}>{c.name}</Text>
<Text style={styles.gridDates}>{c.dates}</Text>
</TouchableOpacity>
</Animated.View>
))}
</View>
</ScrollView>
);
};
星座网格显示所有 12 个星座。
网格布局:flexDirection: 'row' 和 flexWrap: 'wrap' 让卡片自动换行。
卡片动画:透明度和缩放从 0 到 1。
卡片内容:
- 星座符号:字号 28
- 星座名称:白色,字号 12
- 日期范围:灰色,字号 10
激活状态:
- 选中的卡片用紫色背景,带阴影
- 选中的卡片名称加粗
为什么显示日期范围?因为用户可能不记得自己的星座,看到日期范围能快速找到。比如"我是 3 月 25 日出生,看到白羊座是 3/21-4/19,就知道我是白羊座"。
鸿蒙 ArkTS 对比:星座计算
@State month: string = ''
@State day: string = ''
@State result: any = null
getConstellation(m: number, d: number) {
const dates = [[20, 19], [19, 20], [21, 20], [20, 21], [21, 22], [22, 23], [23, 23], [23, 23], [23, 22], [24, 22], [23, 21], [22, 20]]
const index = d < dates[m - 1][0] ? (m + 10) % 12 : (m + 11) % 12
return constellations[index]
}
search() {
const m = parseInt(this.month)
const d = parseInt(this.day)
if (m >= 1 && m <= 12 && d >= 1 && d <= 31) {
this.result = this.getConstellation(m, d)
}
}
getElementColor(element: string): string {
switch (element) {
case '火': return '#e74c3c'
case '土': return '#8b4513'
case '风': return '#3498db'
case '水': return '#1abc9c'
default: return '#4A90D9'
}
}
ArkTS 中的星座计算逻辑完全一样。核心是日期分界点判断和取余运算。parseInt()、switch 都是标准 JavaScript 语法,跨平台通用。
样式定义
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0f0f23', padding: 16 },
header: { alignItems: 'center', marginBottom: 24 },
headerEmoji: { fontSize: 50, marginBottom: 8 },
headerTitle: { fontSize: 28, fontWeight: '700', color: '#fff' },
headerSubtitle: { fontSize: 14, color: '#888', marginTop: 4 },
inputSection: { flexDirection: 'row', marginBottom: 20, alignItems: 'flex-end' },
inputGroup: { flex: 1, marginRight: 8 },
label: { fontSize: 12, color: '#888', marginBottom: 4 },
input: { backgroundColor: '#1a1a3e', color: '#fff', padding: 14, borderRadius: 12, fontSize: 18, textAlign: 'center', borderWidth: 1, borderColor: '#3a3a6a' },
btn: { backgroundColor: '#6c5ce7', paddingVertical: 14, paddingHorizontal: 24, borderRadius: 12, shadowColor: '#6c5ce7', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.4, shadowRadius: 8, elevation: 6 },
btnText: { color: '#fff', fontWeight: '700' },
result: { backgroundColor: '#1a1a3e', padding: 24, borderRadius: 20, alignItems: 'center', marginBottom: 24, borderWidth: 2, shadowOffset: { width: 0, height: 8 }, shadowOpacity: 0.4, shadowRadius: 16, elevation: 10 },
emoji: { fontSize: 60, marginBottom: 8 },
name: { fontSize: 28, fontWeight: '700', color: '#fff', marginVertical: 8 },
dates: { color: '#888', marginBottom: 16 },
infoRow: { flexDirection: 'row', width: '100%', paddingVertical: 10, borderTopWidth: 1, borderTopColor: '#3a3a6a' },
infoLabel: { color: '#888', width: 60 },
infoValue: { color: '#fff', flex: 1 },
grid: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'center' },
gridItem: { width: 100, margin: 4, backgroundColor: '#1a1a3e', padding: 12, borderRadius: 16, alignItems: 'center', borderWidth: 1, borderColor: '#3a3a6a' },
gridItemActive: { backgroundColor: '#6c5ce7', borderColor: '#6c5ce7', shadowColor: '#6c5ce7', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.5, shadowRadius: 8, elevation: 6 },
gridEmoji: { fontSize: 28 },
gridName: { fontSize: 12, color: '#fff', marginTop: 4 },
gridNameActive: { fontWeight: '600' },
gridDates: { fontSize: 10, color: '#666' },
});
容器用深蓝黑色背景。输入区域横向布局,底部对齐。按钮用紫色背景,带阴影。结果卡片边框颜色根据元素动态设置。星座网格每个卡片宽度 100。选中的卡片用紫色背景,带阴影。
小结
这个星座查询工具展示了星座计算和元素分类的实现。用日期分界点判断星座,根据元素设置不同颜色。旋转动画让星座符号旋转一圈,网格布局显示 12 个星座。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)