类型语义与状态边界

  • 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),一起共建开源鸿蒙跨平台生态。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐