React Native鸿蒙跨平台子项渲染配合 React.memo 与 useCallback 固定 renderItem,减少父级状态微变更导致的大面积重绘
本文介绍了在React Native中集成鸿蒙(HarmonyOS)组件的开发方法。主要内容包括:1)鸿蒙开发基础,需掌握DevEco Studio、HarmonyOS SDK及Java/Kotlin开发;2)三种集成方式:通过WebView加载网页版应用、使用Native Modules桥接原生代码、利用Deep Linking传递数据;3)职业发展建议,包括学习鸿蒙API、项目实践和持续优化。
类型语义与状态边界
- Song、Message、Playlist 三类 TypeScript 类型将“播放域”“消息域”“歌单域”的数据协议固定在 JS 层。组件只消费这些纯对象,避免平台特化字段进入渲染路径,Bridge 传输稳定,ArkUI/UIKit/View 三端更易维持一致性。
- MusicPlayer、SongCard、MessageCard、PlaylistCard 均遵循“哑组件 + 父级调度”的边界:数据通过 props 下发,意图通过回调上报,页面级状态域负责不可变更新。这种边界让 JS 线程的逻辑与原生视图树的批处理解耦,跨端交互表现更可控。
播放器组件的控制流与索引语义
- 播放器以 currentSong + isPlaying 驱动 UI,播放控制由 onPlayPause/onNext/onPrevious 以事件上报的形式回到页面状态域。循环切歌使用 findIndex → 模运算计算目标下标,语义清晰但每次交互都做线性搜索。
- 在高频切歌场景建议以“当前下标”作为单一信号源,切歌 O(1) 且对列表变更更鲁棒;currentSong 仅作派生显示,避免因对象比较导致不可预期的重渲染。
const [index, setIndex] = useState<number>(-1);
const [isPlaying, setIsPlaying] = useState(false);
const playSong = (song: Song) => {
const i = songs.findIndex(s => s.id === song.id);
setIndex(i);
setIsPlaying(true);
};
const nextSong = () => setIndex(i => (i < 0 ? 0 : (i + 1) % songs.length));
const previousSong = () => setIndex(i => (i < 0 ? 0 : (i - 1 + songs.length) % songs.length));
const currentSong = index >= 0 ? songs[index] : null;
- 如需接入真实音频,跨端要选用成熟桥接方案(如 react-native-track-player 或 Expo AV),并验证鸿蒙端的后端驱动与权限模型;UI 控制与播放引擎应通过单向数据流对齐,避免 JS 线程与原生播放状态漂移。
列表虚拟化与滚动容器
- 页面外层使用 ScrollView 包裹两个 FlatList(消息、歌曲),属于嵌套滚动的典型反模式,容易引发事件竞争与重复布局。跨端稳健做法是让单个 FlatList 接管整页滚动,将播放器、快捷操作、推荐歌单作为 ListHeaderComponent 注入,功能介绍作为 ListFooterComponent 注入,形成单一滚动容器。
- 子项渲染配合 React.memo 与 useCallback 固定 renderItem,减少父级状态微变更导致的大面积重绘;ArkUI 下同样能降低视图树重排与合成抖动。
const Header = (
<View>
<MusicPlayer currentSong={currentSong} isPlaying={isPlaying} onPlayPause={togglePlayPause} onNext={nextSong} onPrevious={previousSong} />
<View>{/* 快捷操作/推荐歌单等 */}</View>
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>私信送歌</Text>
<Text style={styles.messageCount}>({messages.filter(m => !m.isRead).length} 条新消息)</Text>
</View>
<FlatList
data={messages}
keyExtractor={m => m.id}
renderItem={({ item }) => <MessageCard message={item} onSendSong={sendSongToUser} onMarkAsRead={markMessageAsRead} />}
scrollEnabled={false}
/>
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>热门歌曲</Text>
<Text style={styles.songCount}>({songs.length} 首歌曲)</Text>
</View>
</View>
);
<FlatList
data={songs}
keyExtractor={s => s.id}
renderItem={({ item }) => <SongCard song={item} onPlay={playSong} onToggleFavorite={toggleFavorite} />}
ListHeaderComponent={Header}
removeClippedSubviews
initialNumToRender={10}
windowSize={5}
/>
不可变更新与性能稳定性
- toggleFavorite、markMessageAsRead 使用闭包中的数组进行 map 更新,在并发交互或批量操作场景存在读取旧快照的风险。函数式 setState 保证以最新快照为基准更新,跨端节流时序也更稳。
const toggleFavorite = (id: string) => {
setSongs(prev => prev.map(s => (s.id === id ? { ...s, isFavorite: !s.isFavorite } : s)));
};
const markMessageAsRead = (id: string) => {
setMessages(prev => prev.map(m => (m.id === id ? { ...m, isRead: true } : m)));
};
- 派生值建议以 useMemo 绑定,降低每次渲染的 JS 计算负担,并让 Bridge 批处理更轻量;例如未读消息数与喜欢数等可以直接从状态域派生。
const unreadCount = useMemo(() => messages.filter(m => !m.isRead).length, [messages]);
交互桥接与动效原生化
- Alert 属于原生弹窗桥接,当前用于发送歌曲提示,不涉及批量更新;需要合并多条变更时应在回调中一次 setState,减少 JS→UI 往返,使 ArkUI/UIKit/View 的批处理更高效。
- 播放按钮、收藏心形、已读标记如需动效,应优先使用 useNativeDriver 的 transform/opacity 属性,让 UI 线程承担插值,避免布局重算与 JS 定时器造成的帧抖动。三端都会从原生驱动中获益。
import { Animated, useRef, useEffect } from 'react-native';
const PlayPulse = ({ active }: { active: boolean }) => {
const scale = useRef(new Animated.Value(1)).current;
useEffect(() => {
Animated.sequence([
Animated.timing(scale, { toValue: active ? 1.15 : 1, duration: 120, useNativeDriver: true }),
Animated.timing(scale, { toValue: 1, duration: 100, useNativeDriver: true }),
]).start();
}, [active]);
return <Animated.Text style={{ transform: [{ scale }] }}>{active ? '⏸️' : '▶️'}</Animated.Text>;
};
图标与资源管线
- 全局图标采用 Emoji 文本,零依赖、桥接简单,但不同平台字体渲染有差异。若追求统一视觉与色彩控制,推荐切换到 react-native-svg 或图标字库;在鸿蒙端需验证 SVG/字体桥接的完整性与性能,并准备文本图标回退。
- 封面占位当前用文本图标,真实业务应使用 Image 并开启缓存/预取,减少滚动中的解码抖动;Harmony 端要验证图片管线的缓存策略与网络权限。
import { Image } from 'react-native';
<Image source={{ uri: currentSong?.cover ?? '' }} style={{ width: 120, height: 120 }} resizeMode="cover" />
核心业务实体建模
音乐社交应用展示了多媒体应用的 TypeScript 类型系统设计:
type Song = {
id: string;
title: string;
artist: string;
album: string;
duration: string;
cover: string;
isFavorite: boolean;
plays: number;
};
type Message = {
id: string;
sender: string;
content: string;
timestamp: string;
songRequest?: string;
isRead: boolean;
};
type Playlist = {
id: string;
name: string;
songs: number;
cover: string;
};
这种类型定义方式在跨平台音乐应用开发中具有重要的架构意义。通过将歌曲、消息、播放列表等核心业务实体进行严格的类型约束,确保了音乐数据结构的正确性和一致性。在鸿蒙平台上,这种模型可以无缝对接鸿蒙的媒体服务系统,实现音频播放、歌词同步等原生能力的深度集成。特别值得注意的是 Message 类型中的可选 songRequest 字段,它为社交功能提供了灵活的扩展空间,体现了类型系统对业务场景的精确表达能力。
状态驱动的播放控制
应用实现了复杂的播放状态管理:
const [currentSong, setCurrentSong] = useState<Song | null>(null);
const [isPlaying, setIsPlaying] = useState(false);
const nextSong = () => {
if (!currentSong) return;
const currentIndex = songs.findIndex(song => song.id === currentSong.id);
const nextIndex = (currentIndex + 1) % songs.length;
setCurrentSong(songs[nextIndex]);
};
const previousSong = () => {
if (!currentSong) return;
const currentIndex = songs.findIndex(song => song.id === currentSong.id);
const prevIndex = (currentIndex - 1 + songs.length) % songs.length;
setCurrentSong(songs[prevIndex]);
};
这种播放状态管理在音乐应用中至关重要。循环播放的算法设计(使用模运算实现无缝循环)是音乐播放器的核心技术之一。在鸿蒙平台上,这种状态管理可以与鸿蒙的分布式音频系统结合,实现多设备间的音乐播放同步。例如,用户在手机上开始播放后,可以在平板或智能音箱上无缝继续收听,提供跨设备的连贯音乐体验。
组件化多媒体界面架构
播放器组件设计
音乐播放器组件采用了多层次的交互设计:
const MusicPlayer = ({
currentSong,
isPlaying,
onPlayPause,
onNext,
onPrevious
}) => {
if (!currentSong) {
return (
<View style={styles.playerContainer}>
<View style={styles.albumArtPlaceholder}>
<Text style={styles.albumArtText}>{ICONS.music}</Text>
</View>
<Text style={styles.placeholderText}>请选择一首歌曲播放</Text>
</View>
);
}
return (
<View style={styles.playerContainer}>
<View style={styles.albumArt}>
<Text style={styles.albumArtText}>{ICONS.music}</Text>
</View>
<View style={styles.songInfo}>
<Text style={styles.songTitle}>{currentSong.title}</Text>
<Text style={styles.songArtist}>{currentSong.artist}</Text>
<Text style={styles.songAlbum}>{currentSong.album}</Text>
</View>
<View style={styles.playerControls}>
<TouchableOpacity onPress={onPrevious}>
<Text style={styles.controlButton}>⏮️</Text>
</TouchableOpacity>
<TouchableOpacity onPress={onPlayPause} style={styles.playButton}>
<Text style={styles.playButtonText}>{isPlaying ? ICONS.pause : ICONS.play}</Text>
</TouchableOpacity>
<TouchableOpacity onPress={onNext}>
<Text style={styles.controlButton}>⏭️</Text>
</TouchableOpacity>
</View>
</View>
);
};
这种播放器设计在跨平台应用中展现了良好的可扩展性。组件通过 props 接收所有控制回调,实现了播放逻辑与 UI 渲染的完全解耦。在鸿蒙平台上,这种设计可以轻松替换为原生音频组件,利用鸿蒙的媒体引擎实现更专业的音频处理,包括均衡器、3D 音效、歌词同步等高级功能。同时,组件的空状态处理(无歌曲时的占位展示)为用户提供了清晰的操作指引。
社交功能组件设计
私信卡片组件展示了音乐社交的创新交互:
const MessageCard = ({ message, onSendSong, onMarkAsRead }) => {
return (
<View style={[styles.messageCard, !message.isRead && styles.unreadMessage]}>
<View style={styles.messageHeader}>
<View style={styles.senderInfo}>
<Text style={styles.senderAvatar}>{ICONS.user}</Text>
<View>
<Text style={styles.senderName}>{message.sender}</Text>
<Text style={styles.timestamp}>{message.timestamp}</Text>
</View>
</View>
{!message.isRead && (
<TouchableOpacity onPress={() => onMarkAsRead(message.id)}>
<Text style={styles.markAsReadButton}>标记已读</Text>
</TouchableOpacity>
)}
</View>
<Text style={styles.messageContent}>{message.content}</Text>
{message.songRequest && (
<View style={styles.songRequestContainer}>
<Text style={styles.songRequestText}>🎵 请求歌曲: {message.songRequest}</Text>
<TouchableOpacity
style={styles.sendSongButton}
onPress={() => onSendSong(message.sender)}
>
<Text style={styles.sendSongButtonText}>发送歌曲</Text>
</TouchableOpacity>
</View>
)}
</View>
);
};
这种社交功能设计在音乐应用场景中具有创新的用户体验价值。未读消息的视觉标识(蓝色左边框)和歌曲请求的快速响应机制,体现了社交音乐应用的核心价值。在鸿蒙平台上,这种社交功能可以与鸿蒙的分布式消息系统深度集成,实现好友间的实时音乐分享和协同播放体验。
鸿蒙跨端适配关键技术
分布式音频同步
鸿蒙的分布式特性为音乐应用带来革命性体验:
// 伪代码:分布式音频同步
const DistributedAudio = {
syncPlaybackState: (currentSong, position, isPlaying) => {
if (Platform.OS === 'harmony') {
harmonyNative.syncAudioAcrossDevices(currentSong, position, isPlaying);
}
},
getCrossDevicePlayback: () => {
if (Platform.OS === 'harmony') {
return harmonyNative.getDistributedPlayback();
}
return localPlayback;
},
enableMultiDeviceAudio: () => {
if (Platform.OS === 'harmony') {
harmonyNative.enableSpeakerGroup();
}
}
};
原生音频服务集成
利用鸿蒙的原生音频服务提升播放体验:
// 伪代码:音频服务集成
const AudioServiceIntegration = {
useNativeAudioEngine: () => {
if (Platform.OS === 'harmony') {
return harmonyNative.integrateAudioEngine();
}
return defaultAudioEngine;
},
enableSpatialAudio: () => {
if (Platform.OS === 'harmony') {
harmonyNative.enableSpatialAudio();
}
},
syncLyrics: (songId) => {
if (Platform.OS === 'harmony') {
harmonyNative.syncLyricsWithPlayback(songId);
}
}
};
音乐社交能力增强
鸿蒙平台为音乐社交提供独特能力:
// 伪代码:音乐社交增强
const SocialFeatures = {
enableMusicSharing: () => {
if (Platform.OS === 'harmony') {
harmonyNative.enableDistributedSharing();
}
},
connectMusicFriends: () => {
if (Platform.OS === 'harmony') {
harmonyNative.integrateFriendActivity();
}
}
};
多媒体性能优化体系
音频资源管理
// 伪代码:音频性能优化
const AudioPerformance = {
optimizeBuffering: () => {
if (Platform.OS === 'harmony') {
harmonyNative.configureAudioBuffer();
}
},
manageMemory: () => {
if (Platform.OS === 'harmony') {
harmonyNative.releaseUnusedAudioResources();
}
}
};
智能化音乐体验
// 伪代码:AI音乐推荐
const IntelligentMusic = {
enableRecommendations: (userBehavior) => {
if (Platform.OS === 'harmony') {
harmonyNative.enableMusicAI();
}
},
predictPlaylists: (listeningHabits) => {
if (Platform.OS === 'harmony') {
return harmonyNative.generateSmartPlaylists(listeningHabits);
}
return defaultPlaylists;
}
};
沉浸式音频体验
// 伪代码:沉浸式音频
const ImmersiveAudio = {
enableSpatialAudio: () => {
if (Platform.OS === 'harmony') {
harmonyNative.setupSpatialAudio();
}
},
connectToSpeakers: () => {
if (Platform.OS === 'harmony') {
harmonyNative.enableSpeakerArray();
}
}
};
真实演示案例代码:
// app.tsx
import React, { useState } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Dimensions, Alert, FlatList } from 'react-native';
// 图标库
const ICONS = {
music: '🎵',
heart: '❤️',
share: '🔄',
message: '💬',
user: '👤',
play: '▶️',
pause: '⏸️',
list: '📋',
};
const { width } = Dimensions.get('window');
// 歌曲类型
type Song = {
id: string;
title: string;
artist: string;
album: string;
duration: string;
cover: string;
isFavorite: boolean;
plays: number;
};
// 私信类型
type Message = {
id: string;
sender: string;
content: string;
timestamp: string;
songRequest?: string;
isRead: boolean;
};
// 播放列表类型
type Playlist = {
id: string;
name: string;
songs: number;
cover: string;
};
// 音乐播放器组件
const MusicPlayer = ({
currentSong,
isPlaying,
onPlayPause,
onNext,
onPrevious
}: {
currentSong: Song | null;
isPlaying: boolean;
onPlayPause: () => void;
onNext: () => void;
onPrevious: () => void;
}) => {
if (!currentSong) {
return (
<View style={styles.playerContainer}>
<View style={styles.albumArtPlaceholder}>
<Text style={styles.albumArtText}>{ICONS.music}</Text>
</View>
<Text style={styles.placeholderText}>请选择一首歌曲播放</Text>
</View>
);
}
return (
<View style={styles.playerContainer}>
<View style={styles.albumArt}>
<Text style={styles.albumArtText}>{ICONS.music}</Text>
</View>
<View style={styles.songInfo}>
<Text style={styles.songTitle}>{currentSong.title}</Text>
<Text style={styles.songArtist}>{currentSong.artist}</Text>
<Text style={styles.songAlbum}>{currentSong.album}</Text>
</View>
<View style={styles.playerControls}>
<TouchableOpacity onPress={onPrevious}>
<Text style={styles.controlButton}>⏮️</Text>
</TouchableOpacity>
<TouchableOpacity onPress={onPlayPause} style={styles.playButton}>
<Text style={styles.playButtonText}>{isPlaying ? ICONS.pause : ICONS.play}</Text>
</TouchableOpacity>
<TouchableOpacity onPress={onNext}>
<Text style={styles.controlButton}>⏭️</Text>
</TouchableOpacity>
</View>
</View>
);
};
// 歌曲卡片组件
const SongCard = ({
song,
onPlay,
onToggleFavorite
}: {
song: Song;
onPlay: (song: Song) => void;
onToggleFavorite: (id: string) => void;
}) => {
return (
<View style={styles.songCard}>
<TouchableOpacity style={styles.songCover} onPress={() => onPlay(song)}>
<Text style={styles.songCoverText}>{ICONS.music}</Text>
</TouchableOpacity>
<View style={styles.songDetails}>
<Text style={styles.songTitle} numberOfLines={1}>{song.title}</Text>
<Text style={styles.songArtist}>{song.artist}</Text>
<Text style={styles.songDuration}>{song.duration}</Text>
</View>
<View style={styles.songActions}>
<Text style={styles.playsText}>{song.plays} 次播放</Text>
<TouchableOpacity onPress={() => onToggleFavorite(song.id)}>
<Text style={[styles.favoriteIcon, song.isFavorite && styles.favoriteIconActive]}>
{song.isFavorite ? ICONS.heart : '♡'}
</Text>
</TouchableOpacity>
</View>
</View>
);
};
// 私信卡片组件
const MessageCard = ({
message,
onSendSong,
onMarkAsRead
}: {
message: Message;
onSendSong: (sender: string) => void;
onMarkAsRead: (id: string) => void;
}) => {
return (
<View style={[styles.messageCard, !message.isRead && styles.unreadMessage]}>
<View style={styles.messageHeader}>
<View style={styles.senderInfo}>
<Text style={styles.senderAvatar}>{ICONS.user}</Text>
<View>
<Text style={styles.senderName}>{message.sender}</Text>
<Text style={styles.timestamp}>{message.timestamp}</Text>
</View>
</View>
{!message.isRead && (
<TouchableOpacity onPress={() => onMarkAsRead(message.id)}>
<Text style={styles.markAsReadButton}>标记已读</Text>
</TouchableOpacity>
)}
</View>
<Text style={styles.messageContent}>{message.content}</Text>
{message.songRequest && (
<View style={styles.songRequestContainer}>
<Text style={styles.songRequestText}>🎵 请求歌曲: {message.songRequest}</Text>
<TouchableOpacity
style={styles.sendSongButton}
onPress={() => onSendSong(message.sender)}
>
<Text style={styles.sendSongButtonText}>发送歌曲</Text>
</TouchableOpacity>
</View>
)}
</View>
);
};
// 播放列表卡片组件
const PlaylistCard = ({ playlist }: { playlist: Playlist }) => {
return (
<View style={styles.playlistCard}>
<View style={styles.playlistCover}>
<Text style={styles.playlistCoverText}>{ICONS.list}</Text>
</View>
<View style={styles.playlistInfo}>
<Text style={styles.playlistName}>{playlist.name}</Text>
<Text style={styles.playlistSongs}>{playlist.songs} 首歌曲</Text>
</View>
</View>
);
};
// 主页面组件
const MusicApp: React.FC = () => {
const [songs, setSongs] = useState<Song[]>([
{
id: '1',
title: '夜曲',
artist: '周杰伦',
album: '十一月的萧邦',
duration: '3:42',
cover: '',
isFavorite: true,
plays: 1245
},
{
id: '2',
title: '稻香',
artist: '周杰伦',
album: '魔杰座',
duration: '3:47',
cover: '',
isFavorite: false,
plays: 2156
},
{
id: '3',
title: '青花瓷',
artist: '周杰伦',
album: '我很忙',
duration: '3:58',
cover: '',
isFavorite: true,
plays: 3421
},
{
id: '4',
title: '告白气球',
artist: '周杰伦',
album: '周杰伦的床边故事',
duration: '3:34',
cover: '',
isFavorite: false,
plays: 4210
},
{
id: '5',
title: '说好的幸福呢',
artist: '周杰伦',
album: '魔杰座',
duration: '3:22',
cover: '',
isFavorite: false,
plays: 1876
},
{
id: '6',
title: '菊花台',
artist: '周杰伦',
album: '黄金甲',
duration: '3:55',
cover: '',
isFavorite: true,
plays: 2341
}
]);
const [messages, setMessages] = useState<Message[]>([
{
id: '1',
sender: '小美',
content: '听说你最近在听周杰伦的新歌,推荐几首给我吧!',
timestamp: '10:30',
songRequest: '夜曲',
isRead: false
},
{
id: '2',
sender: '小明',
content: '上次你分享的那首歌太好听了,还有类似的吗?',
timestamp: '昨天',
songRequest: '稻香',
isRead: true
},
{
id: '3',
sender: '小红',
content: '心情不太好的时候适合听什么歌呢?',
timestamp: '前天',
songRequest: '青花瓷',
isRead: true
}
]);
const [playlists] = useState<Playlist[]>([
{ id: '1', name: '我喜欢的音乐', songs: 42, cover: '' },
{ id: '2', name: '周杰伦精选', songs: 28, cover: '' },
{ id: '3', name: '流行热歌榜', songs: 56, cover: '' },
{ id: '4', name: '放松心情', songs: 15, cover: '' },
]);
const [currentSong, setCurrentSong] = useState<Song | null>(null);
const [isPlaying, setIsPlaying] = useState(false);
const playSong = (song: Song) => {
setCurrentSong(song);
setIsPlaying(true);
};
const toggleFavorite = (id: string) => {
setSongs(songs.map(song =>
song.id === id
? { ...song, isFavorite: !song.isFavorite }
: song
));
};
const togglePlayPause = () => {
setIsPlaying(!isPlaying);
};
const nextSong = () => {
if (!currentSong) return;
const currentIndex = songs.findIndex(song => song.id === currentSong.id);
const nextIndex = (currentIndex + 1) % songs.length;
setCurrentSong(songs[nextIndex]);
};
const previousSong = () => {
if (!currentSong) return;
const currentIndex = songs.findIndex(song => song.id === currentSong.id);
const prevIndex = (currentIndex - 1 + songs.length) % songs.length;
setCurrentSong(songs[prevIndex]);
};
const markMessageAsRead = (id: string) => {
setMessages(messages.map(message =>
message.id === id
? { ...message, isRead: true }
: message
));
};
const sendSongToUser = (userName: string) => {
Alert.alert('发送歌曲', `向 ${userName} 发送了一首歌曲`);
};
return (
<SafeAreaView style={styles.container}>
{/* 头部 */}
<View style={styles.header}>
<Text style={styles.title}>音乐私享</Text>
<View style={styles.headerActions}>
<TouchableOpacity style={styles.messageButton}>
<Text style={styles.messageIcon}>{ICONS.message}</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.userButton}>
<Text style={styles.userIcon}>{ICONS.user}</Text>
</TouchableOpacity>
</View>
</View>
<ScrollView style={styles.content}>
{/* 音乐播放器 */}
<MusicPlayer
currentSong={currentSong}
isPlaying={isPlaying}
onPlayPause={togglePlayPause}
onNext={nextSong}
onPrevious={previousSong}
/>
{/* 快速操作 */}
<View style={styles.quickActions}>
<TouchableOpacity style={styles.quickAction}>
<Text style={styles.quickActionIcon}>{ICONS.heart}</Text>
<Text style={styles.quickActionText}>我喜欢</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.quickAction}>
<Text style={styles.quickActionIcon}>{ICONS.list}</Text>
<Text style={styles.quickActionText}>播放列表</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.quickAction}>
<Text style={styles.quickActionIcon}>{ICONS.share}</Text>
<Text style={styles.quickActionText}>分享</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.quickAction}>
<Text style={styles.quickActionIcon}>{ICONS.music}</Text>
<Text style={styles.quickActionText}>随机播放</Text>
</TouchableOpacity>
</View>
{/* 推荐播放列表 */}
<Text style={styles.sectionTitle}>推荐歌单</Text>
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.playlistsContainer}>
<View style={styles.playlistsList}>
{playlists.map(playlist => (
<PlaylistCard key={playlist.id} playlist={playlist} />
))}
</View>
</ScrollView>
{/* 私信标题 */}
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>私信送歌</Text>
<Text style={styles.messageCount}>({messages.filter(m => !m.isRead).length} 条新消息)</Text>
</View>
{/* 私信列表 */}
<FlatList
data={messages}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<MessageCard
message={item}
onSendSong={sendSongToUser}
onMarkAsRead={markMessageAsRead}
/>
)}
showsVerticalScrollIndicator={false}
/>
{/* 热门歌曲标题 */}
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>热门歌曲</Text>
<Text style={styles.songCount}>({songs.length} 首歌曲)</Text>
</View>
{/* 歌曲列表 */}
<FlatList
data={songs}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<SongCard
song={item}
onPlay={playSong}
onToggleFavorite={toggleFavorite}
/>
)}
showsVerticalScrollIndicator={false}
/>
{/* 功能介绍 */}
<View style={styles.featureCard}>
<Text style={styles.featureTitle}>私信送歌功能</Text>
<Text style={styles.featureDescription}>
• 通过私信接收好友的音乐请求
</Text>
<Text style={styles.featureDescription}>
• 一键发送喜爱的歌曲给好友
</Text>
<Text style={styles.featureDescription}>
• 创建个性化歌单分享给他人
</Text>
<Text style={styles.featureDescription}>
• 收藏好友推荐的精彩音乐
</Text>
</View>
</ScrollView>
{/* 底部导航 */}
<View style={styles.bottomNav}>
<TouchableOpacity style={styles.navItem}>
<Text style={styles.navIcon}>{ICONS.music}</Text>
<Text style={styles.navText}>音乐</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navItem}>
<Text style={styles.navIcon}>{ICONS.list}</Text>
<Text style={styles.navText}>歌单</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.navItem, styles.activeNavItem]}>
<Text style={styles.navIcon}>{ICONS.message}</Text>
<Text style={styles.navText}>私信</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navItem}>
<Text style={styles.navIcon}>{ICONS.user}</Text>
<Text style={styles.navText}>我的</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f8fafc',
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: 20,
backgroundColor: '#ffffff',
borderBottomWidth: 1,
borderBottomColor: '#e2e8f0',
},
title: {
fontSize: 20,
fontWeight: 'bold',
color: '#1e293b',
},
headerActions: {
flexDirection: 'row',
alignItems: 'center',
},
messageButton: {
width: 36,
height: 36,
borderRadius: 18,
backgroundColor: '#f1f5f9',
alignItems: 'center',
justifyContent: 'center',
marginRight: 12,
},
messageIcon: {
fontSize: 18,
color: '#64748b',
},
userButton: {
width: 36,
height: 36,
borderRadius: 18,
backgroundColor: '#f1f5f9',
alignItems: 'center',
justifyContent: 'center',
},
userIcon: {
fontSize: 18,
color: '#64748b',
},
content: {
flex: 1,
padding: 16,
},
playerContainer: {
backgroundColor: '#ffffff',
borderRadius: 16,
padding: 16,
marginBottom: 16,
elevation: 2,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
albumArtPlaceholder: {
width: 120,
height: 120,
borderRadius: 12,
backgroundColor: '#f1f5f9',
alignItems: 'center',
justifyContent: 'center',
alignSelf: 'center',
marginBottom: 16,
},
albumArtText: {
fontSize: 40,
},
placeholderText: {
fontSize: 14,
color: '#64748b',
textAlign: 'center',
},
albumArt: {
width: 120,
height: 120,
borderRadius: 12,
backgroundColor: '#f1f5f9',
alignItems: 'center',
justifyContent: 'center',
alignSelf: 'center',
marginBottom: 16,
},
songInfo: {
alignItems: 'center',
marginBottom: 16,
},
songTitle: {
fontSize: 18,
fontWeight: 'bold',
color: '#1e293b',
marginBottom: 4,
},
songArtist: {
fontSize: 14,
color: '#64748b',
marginBottom: 2,
},
songAlbum: {
fontSize: 12,
color: '#94a3b8',
},
playerControls: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
controlButton: {
fontSize: 24,
marginHorizontal: 20,
color: '#64748b',
},
playButton: {
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: '#3b82f6',
alignItems: 'center',
justifyContent: 'center',
},
playButtonText: {
fontSize: 24,
color: '#ffffff',
},
quickActions: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 16,
},
quickAction: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 16,
alignItems: 'center',
flex: 1,
marginHorizontal: 4,
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
quickActionIcon: {
fontSize: 24,
color: '#3b82f6',
marginBottom: 8,
},
quickActionText: {
fontSize: 12,
color: '#1e293b',
},
playlistsContainer: {
marginBottom: 16,
},
playlistsList: {
flexDirection: 'row',
},
playlistCard: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 12,
marginRight: 12,
width: 140,
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
playlistCover: {
width: 80,
height: 80,
borderRadius: 8,
backgroundColor: '#f1f5f9',
alignItems: 'center',
justifyContent: 'center',
alignSelf: 'center',
marginBottom: 8,
},
playlistCoverText: {
fontSize: 24,
},
playlistInfo: {
alignItems: 'center',
},
playlistName: {
fontSize: 14,
fontWeight: '500',
color: '#1e293b',
marginBottom: 4,
},
playlistSongs: {
fontSize: 12,
color: '#64748b',
},
sectionTitle: {
fontSize: 18,
fontWeight: 'bold',
color: '#1e293b',
marginVertical: 12,
},
sectionHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
messageCount: {
fontSize: 14,
color: '#64748b',
},
songCount: {
fontSize: 14,
color: '#64748b',
},
messageCard: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 16,
marginBottom: 12,
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
unreadMessage: {
borderLeftWidth: 4,
borderLeftColor: '#3b82f6',
},
messageHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 8,
},
senderInfo: {
flexDirection: 'row',
alignItems: 'center',
},
senderAvatar: {
width: 32,
height: 32,
borderRadius: 16,
backgroundColor: '#dbeafe',
alignItems: 'center',
justifyContent: 'center',
marginRight: 12,
},
senderName: {
fontSize: 14,
fontWeight: 'bold',
color: '#1e293b',
},
timestamp: {
fontSize: 12,
color: '#94a3b8',
},
markAsReadButton: {
fontSize: 12,
color: '#64748b',
backgroundColor: '#f1f5f9',
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 12,
},
messageContent: {
fontSize: 14,
color: '#334155',
lineHeight: 20,
marginBottom: 12,
},
songRequestContainer: {
backgroundColor: '#f0f9ff',
borderRadius: 8,
padding: 12,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
songRequestText: {
fontSize: 14,
color: '#0369a1',
flex: 1,
},
sendSongButton: {
backgroundColor: '#3b82f6',
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 6,
},
sendSongButtonText: {
color: '#ffffff',
fontSize: 12,
fontWeight: '500',
},
songCard: {
backgroundColor: '#ffffff',
borderRadius: 12,
flexDirection: 'row',
padding: 12,
marginBottom: 12,
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
songCover: {
width: 50,
height: 50,
borderRadius: 8,
backgroundColor: '#f1f5f9',
alignItems: 'center',
justifyContent: 'center',
marginRight: 12,
},
songCoverText: {
fontSize: 20,
},
songDetails: {
flex: 1,
justifyContent: 'center',
},
songTitle: {
fontSize: 14,
fontWeight: '500',
color: '#1e293b',
marginBottom: 4,
},
songArtist: {
fontSize: 12,
color: '#64748b',
marginBottom: 2,
},
songDuration: {
fontSize: 10,
color: '#94a3b8',
},
songActions: {
justifyContent: 'space-between',
alignItems: 'flex-end',
},
playsText: {
fontSize: 10,
color: '#94a3b8',
marginBottom: 4,
},
favoriteIcon: {
fontSize: 18,
color: '#94a3b8',
},
favoriteIconActive: {
color: '#ef4444',
},
featureCard: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 16,
marginTop: 16,
elevation: 1,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
featureTitle: {
fontSize: 16,
fontWeight: 'bold',
color: '#1e293b',
marginBottom: 8,
},
featureDescription: {
fontSize: 12,
color: '#64748b',
lineHeight: 18,
marginBottom: 4,
},
bottomNav: {
flexDirection: 'row',
justifyContent: 'space-around',
backgroundColor: '#ffffff',
borderTopWidth: 1,
borderTopColor: '#e2e8f0',
paddingVertical: 12,
},
navItem: {
alignItems: 'center',
flex: 1,
},
activeNavItem: {
paddingBottom: 2,
borderBottomWidth: 2,
borderBottomColor: '#3b82f6',
},
navIcon: {
fontSize: 20,
color: '#94a3b8',
marginBottom: 4,
},
activeNavIcon: {
color: '#3b82f6',
},
navText: {
fontSize: 12,
color: '#94a3b8',
},
activeNavText: {
color: '#3b82f6',
fontWeight: '500',
},
});
export default MusicApp;

打包
接下来通过打包命令npn run harmony将reactNative的代码打包成为bundle,这样可以进行在开源鸿蒙OpenHarmony中进行使用。

打包之后再将打包后的鸿蒙OpenHarmony文件拷贝到鸿蒙的DevEco-Studio工程目录去:

最后运行效果图如下显示:
欢迎大家加入[开源鸿蒙跨平台
开发者社区](https://openharmonycrossplatform.csdn.net),一起共建开源鸿蒙跨平台生态。
更多推荐
所有评论(0)