在移动应用开发中,游戏应用是一类特殊的应用类型,需要考虑游戏展示、分类筛选、用户交互等多个方面。本文将深入分析一个功能完备的 React Native 游戏应用实现,探讨其架构设计、状态管理、组件设计以及跨端兼容性策略。

组件化

该实现采用了清晰的组件化架构,主要包含以下部分:

  • 主应用组件 (CardGameApp) - 负责整体布局和状态管理
  • 游戏卡片组件 (renderGameCard) - 负责渲染单个游戏卡片
  • 特色游戏组件 - 负责渲染特色游戏卡片
  • 搜索组件 - 提供游戏搜索功能
  • 分类筛选组件 - 提供游戏分类筛选功能

这种架构设计使得代码结构清晰,易于维护和扩展。主应用组件负责管理全局状态,而各个功能组件负责具体的 UI 渲染,实现了关注点分离。

状态管理

CardGameApp 组件使用 useState 钩子管理多个状态:

const [games] = useState<Game[]>([...]);
const [featuredGames] = useState<Game[]>([...]);
const [categories] = useState([...]);
const [selectedCategory, setSelectedCategory] = useState<string>('all');

这种状态管理方式简洁高效,通过状态更新触发组件重新渲染,实现了游戏分类筛选功能。使用 TypeScript 类型定义确保了数据结构的类型安全,减少了运行时错误的可能性。


类型定义

该实现使用 TypeScript 定义了核心数据类型:

type Game = {
  id: string;
  title: string;
  description: string;
  players: string;
  category: string;
  icon: string;
  rating: number;
  isNew: boolean;
};

这个类型定义包含了游戏的完整信息,包括:

  • id - 唯一标识符
  • title - 游戏标题
  • description - 游戏描述
  • players - 玩家数量
  • category - 游戏分类
  • icon - 游戏图标
  • rating - 游戏评分
  • isNew - 是否为新游戏

这种类型定义使得数据结构更加清晰,提高了代码的可读性和可维护性,同时也提供了类型安全保障。

数据组织

应用数据按照功能模块进行组织:

  • games - 完整的游戏列表
  • featuredGames - 特色游戏列表
  • categories - 游戏分类列表
  • selectedCategory - 当前选中的分类

这种数据组织方式使得数据管理更加清晰,易于扩展和维护。通过 filteredGames 变量,实现了根据选中分类筛选游戏的功能:

const filteredGames = selectedCategory === 'all' 
  ? games 
  : games.filter(game => game.category === selectedCategory);

界面设计与用户体验

布局结构

应用界面采用了清晰的层次结构:

  • 头部 - 显示应用标题和搜索按钮
  • 搜索栏 - 提供游戏搜索功能
  • 特色游戏轮播 - 显示推荐的特色游戏
  • 分类筛选 - 提供游戏分类的快速筛选
  • 游戏列表 - 显示筛选后的游戏列表

这种布局结构符合用户的使用习惯,用户可以快速了解应用内容并找到感兴趣的游戏。


针对上述问题,该实现采用了以下适配策略:

  1. 使用 React Native 核心组件 - 优先使用 React Native 内置的组件,如 View、Text、TouchableOpacity、ScrollView 等
  2. 统一的样式定义 - 使用 StyleSheet.create 定义样式,确保样式在不同平台上的一致性
  3. 平台无关的图标 - 使用 Unicode 表情符号作为游戏图标,避免使用平台特定的图标库
  4. Base64 图标 - 提供 Base64 编码的图标作为备选,确保图标在不同平台上的一致性
  5. 简化的交互逻辑 - 使用简单直接的交互逻辑,减少平台差异带来的问题
  6. 滚动优化 - 在横向滚动视图中禁用滚动指示器,提高跨平台一致性—

类型安全

使用 TypeScript 类型定义确保了代码的类型安全:

type Game = {
  id: string;
  title: string;
  description: string;
  players: string;
  category: string;
  icon: string;
  rating: number;
  isNew: boolean;
};

类型定义不仅提高了代码的可读性,也减少了运行时错误的可能性,对于团队协作尤为重要。


当前实现使用 ScrollView 渲染游戏列表,可以考虑使用 FlatList 提高性能:

// 优化前
<ScrollView style={styles.content}>
  {/* 游戏列表 */}
  {filteredGames.map(game => (
    <GameCard key={game.id} game={game} />
  ))}
</ScrollView>

// 优化后
<FlatList
  data={filteredGames}
  renderItem={renderGameCard}
  keyExtractor={item => item.id}
  ListHeaderComponent={
    <>
      {/* 搜索栏 */}
      <View style={styles.searchContainer}>
        {/* 搜索栏内容 */}
      </View>

      {/* 特色游戏轮播 */}
      <Text style={styles.sectionTitle}>特色推荐</Text>
      <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.featuredContainer}>
        {/* 特色游戏内容 */}
      </ScrollView>

      {/* 分类筛选 */}
      <Text style={styles.sectionTitle}>游戏分类</Text>
      <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.categoryContainer}>
        {/* 分类筛选内容 */}
      </ScrollView>

      {/* 游戏列表标题 */}
      <Text style={styles.sectionTitle}>游戏列表</Text>
    </>
  }
  style={styles.content}
/>

2. 状态管理

当前实现使用多个 useState 钩子管理状态,可以考虑使用 useReducer 或状态管理库来管理复杂状态:

// 优化前
const [games] = useState<Game[]>([...]);
const [featuredGames] = useState<Game[]>([...]);
const [categories] = useState([...]);
const [selectedCategory, setSelectedCategory] = useState<string>('all');

// 优化后
const initialState = {
  games: [...],
  featuredGames: [...],
  categories: [...],
  selectedCategory: 'all',
  searchQuery: ''
};

type Action = 
  | { type: 'SET_CATEGORY'; payload: string }
  | { type: 'SET_SEARCH_QUERY'; payload: string }
  | { type: 'ADD_GAME'; payload: Game };

const gameReducer = (state: typeof initialState, action: Action) => {
  switch (action.type) {
    case 'SET_CATEGORY':
      return {
        ...state,
        selectedCategory: action.payload
      };
    case 'SET_SEARCH_QUERY':
      return {
        ...state,
        searchQuery: action.payload
      };
    case 'ADD_GAME':
      return {
        ...state,
        games: [...state.games, action.payload]
      };
    default:
      return state;
  }
};

const [state, dispatch] = useReducer(gameReducer, initialState);

3. 动画

可以为游戏卡片和特色游戏添加动画效果,提升用户体验:

import { Animated } from 'react-native';

const GameCard = ({ game }: { game: Game }) => {
  const scaleAnim = useRef(new Animated.Value(1)).current;
  
  const handlePressIn = () => {
    Animated.spring(scaleAnim, {
      toValue: 0.95,
      useNativeDriver: true
    }).start();
  };
  
  const handlePressOut = () => {
    Animated.spring(scaleAnim, {
      toValue: 1,
      friction: 3,
      tension: 40,
      useNativeDriver: true
    }).start();
  };
  
  return (
    <Animated.View style={{ transform: [{ scale: scaleAnim }] }}>
      <TouchableOpacity
        style={styles.gameCard}
        onPressIn={handlePressIn}
        onPressOut={handlePressOut}
        onPress={() => Alert.alert('游戏详情', `正在启动 ${game.title}`)}
      >
        {/* 游戏卡片内容 */}
      </TouchableOpacity>
    </Animated.View>
  );
};

4. 导航系统

可以集成 React Navigation 实现游戏详情页面的导航:

import { createStackNavigator } from '@react-navigation/stack';

const Stack = createStackNavigator();

const App = () => {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen 
          name="Home" 
          component={CardGameApp} 
          options={{ title: '万能游戏库' }} 
        />
        <Stack.Screen 
          name="GameDetail" 
          component={GameDetailScreen} 
          options={({ route }) => ({ title: route.params?.gameTitle || '游戏详情' })} 
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
};

const GameDetailScreen = ({ route }: { route: any }) => {
  const { gameId } = route.params;
  // 获取游戏详情并渲染
  return (
    <View style={styles.detailContainer}>
      {/* 游戏详情内容 */}
    </View>
  );
};

本文深入分析了一个功能完备的 React Native 游戏应用实现,从架构设计、状态管理、数据结构到跨端兼容性都进行了详细探讨。该实现不仅功能完整,而且代码结构清晰,具有良好的可扩展性和可维护性。


理解这个基于 React Native 开发的万能游戏库应用的核心技术实现逻辑,同时掌握将其适配到鸿蒙(HarmonyOS)平台的关键要点。该应用是典型的移动端内容聚合类应用,融合了多维度数据展示、分类筛选、流式布局、横向/纵向列表、卡片化设计等移动端核心开发范式,是从 React Native 向鸿蒙跨端迁移的典型案例。

该游戏库应用的核心价值在于结构化的游戏数据管理灵活的列表渲染体系状态驱动的分类筛选交互,完全符合移动端内容聚合类应用的开发最佳实践,其技术架构可直接复用至鸿蒙平台。

1. 类型系统

应用采用 TypeScript 强类型设计,构建了标准化的游戏数据模型,为跨端适配提供了坚实的基础:

  • 核心类型定义
    type Game = {
      id: string;          // 唯一标识
      title: string;       // 游戏名称
      description: string; // 游戏描述
      players: string;     // 玩家数量
      category: string;    // 游戏分类
      icon: string;        // 图标(Emoji)
      rating: number;      // 评分
      isNew: boolean;      // 是否新品
    };
    
  • 数据模型设计亮点
    • 多维度属性:覆盖展示(title/description/icon)、分类(category)、筛选(isNew)、交互(rating)等全场景属性;
    • 轻量级视觉标识:使用 Emoji 替代图片资源作为图标,减少包体积,提升加载性能,同时保证跨平台视觉一致性;
    • 标准化分类体系:统一的 category 字段为分类筛选提供了精准的数据源;
  • 类型设计价值
    • 强类型约束避免了运行时数据错误,如 filteredGames 筛选逻辑中通过 category 精准匹配;
    • 标准化的数据结构使跨端迁移时数据层可 100% 复用,仅需适配视图层渲染逻辑;
    • 分层数据管理:将游戏数据分为 games(全量)、featuredGames(推荐),符合内容聚合类应用的数据组织逻辑。

2. 状态管理

应用采用 React Hooks 实现轻量级状态管理,聚焦核心的分类筛选交互,实现高效的状态驱动UI更新:

  • 核心状态设计
    // 全量游戏数据(静态数据,仅初始化)
    const [games] = useState<Game[]>([/* 游戏数据 */]);
    // 推荐游戏数据(静态数据)
    const [featuredGames] = useState<Game[]>([/* 推荐数据 */]);
    // 分类数据(静态数据)
    const [categories] = useState([/* 分类数据 */]);
    // 当前选中的分类(核心交互状态)
    const [selectedCategory, setSelectedCategory] = useState<string>('all');
    
  • 核心筛选逻辑
    const filteredGames = selectedCategory === 'all' 
      ? games 
      : games.filter(game => game.category === selectedCategory);
    
  • 筛选逻辑亮点
    • 惰性计算:仅在 selectedCategory 变化时重新计算 filteredGames,避免不必要的性能开销;
    • 精准匹配:通过 category 字段实现精准的分类筛选,无冗余逻辑;
    • 状态驱动:selectedCategory 作为单一数据源,同时驱动分类选中样式、游戏列表、游戏数量统计等UI状态;
    • 无副作用设计:所有状态更新均为纯函数逻辑,无异步操作,保证状态更新的可预测性。

3. 多维度列表

应用实现了移动端内容聚合类应用的全套列表渲染方案,是跨端适配的核心难点与重点:

  • 横向滚动列表(特色推荐)
    <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.featuredContainer}>
      {featuredGames.map(game => (/* 推荐游戏卡片 */))}
    </ScrollView>
    
    • 核心特性:horizontal 实现横向滚动,showsHorizontalScrollIndicator={false} 隐藏滚动条,提升视觉体验;
    • 尺寸适配:使用 width: width * 0.6 实现响应式宽度,适配不同屏幕尺寸;
  • 横向分类导航
    <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.categoryContainer}>
      {categories.map(category => (/* 分类项 */))}
    </ScrollView>
    
    • 交互设计:点击分类项更新 selectedCategory,同时通过条件样式实现选中状态的视觉差异化;
  • 双列网格列表(游戏列表)
    <FlatList
      data={filteredGames}
      renderItem={renderGameCard}
      keyExtractor={item => item.id}
      showsVerticalScrollIndicator={false}
      numColumns={2}
      columnWrapperStyle={styles.columnWrapper}
    />
    
    • 核心特性:
      • numColumns={2} 实现双列网格布局;
      • columnWrapperStyle 控制列间距;
      • 响应式宽度:width: (width - 48) / 2 实现等宽双列布局,适配不同屏幕;
      • 性能优化:showsVerticalScrollIndicator={false} 隐藏滚动条,提升视觉体验;
  • 列表渲染亮点
    • 分层渲染:根据内容类型选择不同的列表组件(ScrollView/FlatList),兼顾性能与灵活性;
    • 统一的卡片化设计:所有列表项均采用卡片化样式(白色背景 + 圆角 + 阴影),保证视觉一致性;
    • 精准的尺寸计算:通过屏幕宽度计算列表项尺寸,实现完美的响应式布局。

4. 样式系统

应用的样式系统遵循移动端内容聚合类应用的设计规范,注重视觉层次感和交互反馈:

  • 卡片化设计体系
    • 统一的卡片样式:backgroundColor: '#ffffff' + borderRadius: 12 + shadow/elevation
    • 分层阴影:统一的阴影参数(radius: 2, opacity: 0.1, offsetY: 1),保证视觉层级一致;
  • 选中状态差异化
    <TouchableOpacity
      style={[
        styles.categoryItem,
        selectedCategory === category.id && styles.selectedCategory
      ]}
    >
      <Text style={[
        styles.categoryIcon,
        selectedCategory === category.id && styles.selectedCategoryIcon
      ]}>
        {category.icon}
      </Text>
      <Text style={[
        styles.categoryText,
        selectedCategory === category.id && styles.selectedCategoryText
      ]}>
        {category.name}
      </Text>
    </TouchableOpacity>
    
    • 多维度状态反馈:背景色、图标颜色、文字颜色同步变化,提供明确的选中反馈;
  • 响应式尺寸计算
    gameCard: {
      width: (width - 48) / 2, // 双列布局宽度计算
    },
    featuredCard: {
      width: width * 0.6,      // 推荐卡片宽度
    }
    
    • 基于屏幕宽度的动态尺寸计算,保证在不同尺寸设备上的布局一致性;
  • 视觉层级设计
    • 文本层级:标题(20px粗体)> 子标题(18px粗体)> 正文(16px)> 辅助文本(14px/12px);
    • 色彩层级:主色(#3b82f6)> 文本主色(#1e293b)> 文本次色(#64748b)> 文本辅助色(#94a3b8);
    • 空间层级:卡片内边距(16px)> 组件间距(12px/16px)> 文本间距(4px/8px)。

将该 React Native 游戏库应用适配到鸿蒙平台,核心是将 React Native 的组件模型、列表渲染逻辑、状态管理映射到鸿蒙 ArkTS + ArkUI 生态,以下是完整的适配方案和关键技术点。

1. 技术栈与核心API映射

鸿蒙端采用 ArkTS 语言 + ArkUI 组件库(Stage模型),与 React Native 的核心 API 映射关系如下:

React Native 核心API 鸿蒙 ArkTS 对应实现 适配关键说明
useState @State/@LocalStorageProp 状态声明语法替换,逻辑一致
SafeAreaView SafeArea 组件 + safeArea(true) 安全区域适配
View Column/Row/Stack 基础布局组件替换
Text Text 组件 属性基本兼容,样式语法调整
TouchableOpacity Button + stateEffect(false) 可点击组件替换,去除默认效果
ScrollView (vertical) Scroll 组件 纵向滚动容器替换
ScrollView (horizontal) Scroll + scrollDirection(Horizontal) 横向滚动适配
FlatList (numColumns=2) Grid + GridItem 双列网格布局适配
StyleSheet.create @Styles/@Extend + 内联样式 样式体系重构
Alert.alert AlertDialog 弹窗交互替换
Dimensions.get('window') viewportWidth/viewportHeight 屏幕尺寸获取
条件渲染(&&) if 语句 条件渲染语法适配
数组 map/filter ForEach + 过滤函数 数组操作适配

2. 鸿蒙端

// index.ets - 鸿蒙端入口文件
@Entry
@Component
struct CardGameApp {
  // 核心状态 - 对应 React Native 的 useState
  @State games: Game[] = [/* 全量游戏数据 */];
  @State featuredGames: Game[] = [/* 推荐游戏数据 */];
  @State categories: Category[] = [/* 分类数据 */];
  @State selectedCategory: string = 'all';
  
  // 屏幕尺寸 - 对应 React Native 的 Dimensions
  private screenWidth: number = viewportWidth;
  private screenHeight: number = viewportHeight;

  // 类型定义(完全复用 React Native 的类型)
  type Game = {
    id: string;
    title: string;
    description: string;
    players: string;
    category: string;
    icon: string;
    rating: number;
    isNew: boolean;
  };

  type Category = {
    id: string;
    name: string;
    icon: string;
  };

  // 通用样式封装 - 替代 React Native 的 StyleSheet
  @Styles
  cardShadow() {
    .shadow({ radius: 2, color: '#000', opacity: 0.1, offsetX: 0, offsetY: 1 });
  }

  // 筛选游戏(对应 React Native 的 filteredGames)
  private get filteredGames(): Game[] {
    return this.selectedCategory === 'all' 
      ? this.games 
      : this.games.filter(game => game.category === this.selectedCategory);
  }

  build() {
    Column({ space: 0 }) {
      // 头部组件
      this.Header();
      
      // 内容区域
      this.ContentArea();
      
      // 底部导航
      this.BottomNav();
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f8fafc')
    .safeArea(true); // 对应 React Native 的 SafeAreaView
  }
}
(1)头部与底部导航
// 鸿蒙端头部组件
@Builder
Header() {
  Row() {
    Text('万能游戏库')
      .fontSize(20)
      .fontWeight(FontWeight.Bold)
      .fontColor('#1e293b');
    
    // 搜索按钮 - 替代 React Native 的 TouchableOpacity
    Button('🔍')
      .fontSize(20)
      .fontColor('#64748b')
      .backgroundColor(Color.Transparent)
      .stateEffect(false) // 去除默认点击效果
      .onClick(() => {
        AlertDialog.show({
          title: '搜索',
          message: '打开搜索功能',
          confirm: { value: '确定' }
        });
      });
  }
  .justifyContent(FlexAlign.SpaceBetween)
  .alignItems(Alignment.Center)
  .padding(20)
  .backgroundColor('#ffffff')
  .borderBottom({ width: 1, color: '#e2e8f0' })
  .width('100%');
}

// 鸿蒙端底部导航
@Builder
BottomNav() {
  Row({ space: 0 }) {
    // 游戏
    this.NavItem('🎮', '游戏');
    // 排行
    this.NavItem('🏆', '排行');
    // 商城
    this.NavItem('🎁', '商城');
    // 我的
    this.NavItem('👤', '我的');
  }
  .backgroundColor('#ffffff')
  .borderTop({ width: 1, color: '#e2e8f0' })
  .paddingVertical(12)
  .justifyContent(FlexAlign.SpaceAround)
  .width('100%');
}

// 通用导航项
@Builder
NavItem(icon: string, text: string) {
  Button() {
    Column({ space: 4 }) {
      Text(icon)
        .fontSize(20)
        .fontColor('#94a3b8');
      Text(text)
        .fontSize(12)
        .fontColor('#94a3b8');
    }
  }
  .backgroundColor(Color.Transparent)
  .flex(1)
  .stateEffect(false);
}
(2)内容区域
// 鸿蒙端内容区域
@Builder
ContentArea() {
  Scroll() {
    Column({ space: 16 }) {
      // 搜索栏
      this.SearchBar();
      
      // 特色推荐
      this.FeaturedGames();
      
      // 分类导航
      this.CategoryNav();
      
      // 游戏列表
      this.GameList();
      
      // 活动横幅
      this.PromoBanner();
    }
    .padding(16)
    .width('100%');
  }
  .flex(1)
  .width('100%');
}

// 搜索栏
@Builder
SearchBar() {
  Row({ space: 12 }) {
    Text('🔍')
      .fontSize(18)
      .fontColor('#94a3b8');
    
    Text('搜索游戏或关键词')
      .fontSize(14)
      .fontColor('#94a3b8')
      .flex(1);
  }
  .backgroundColor('#ffffff')
  .borderRadius(20)
  .padding({ top: 12, bottom: 12, left: 16, right: 16 })
  .cardShadow() // 应用通用阴影样式
  .width('100%')
  .marginBottom(16);
}

// 特色推荐(横向滚动)
@Builder
FeaturedGames() {
  Column({ space: 12 }) {
    Text('特色推荐')
      .fontSize(18)
      .fontWeight(FontWeight.Bold)
      .fontColor('#1e293b')
      .width('100%');
    
    // 横向滚动容器 - 对应 React Native 的 ScrollView horizontal
    Scroll({ scrollDirection: ScrollDirection.Horizontal }) {
      Row({ space: 12 }) {
        ForEach(this.featuredGames, (game) => {
          // 推荐游戏卡片
          Button() {
            Column({ space: 12 }) {
              // 图标容器
              Column()
                .width(50)
                .height(50)
                .borderRadius(25)
                .backgroundColor('#f1f5f9')
                .justifyContent(FlexAlign.Center)
                .alignItems(Alignment.Center)
                .overlay(Text(game.icon).fontSize(24));
              
              Text(game.title)
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .fontColor('#1e293b');
              
              Text(`⭐ ${game.rating}`)
                .fontSize(14)
                .fontColor('#64748b');
            }
          }
          .backgroundColor('#ffffff')
          .borderRadius(12)
          .padding(16)
          .width(this.screenWidth * 0.6) // 响应式宽度
          .cardShadow()
          .stateEffect(false)
          .onClick(() => {
            AlertDialog.show({
              title: '特色游戏',
              message: `正在启动 ${game.title}`,
              confirm: { value: '确定' }
            });
          });
        });
      }
      .width('auto') // 自适应宽度
      .marginBottom(16);
    }
    .scrollBar(BarState.Off) // 隐藏滚动条
    .width('100%');
  }
}

// 分类导航(横向滚动)
@Builder
CategoryNav() {
  Column({ space: 12 }) {
    Text('游戏分类')
      .fontSize(18)
      .fontWeight(FontWeight.Bold)
      .fontColor('#1e293b')
      .width('100%');
    
    // 横向滚动容器
    Scroll({ scrollDirection: ScrollDirection.Horizontal }) {
      Row({ space: 12 }) {
        ForEach(this.categories, (category) => {
          // 分类项
          Button() {
            Row({ space: 6 }) {
              Text(category.icon)
                .fontSize(18)
                .fontColor(this.selectedCategory === category.id ? '#ffffff' : '#64748b');
              
              Text(category.name)
                .fontSize(14)
                .fontColor(this.selectedCategory === category.id ? '#ffffff' : '#64748b');
            }
          }
          .backgroundColor(this.selectedCategory === category.id ? '#3b82f6' : '#f1f5f9')
          .borderRadius(20)
          .padding({ top: 10, bottom: 10, left: 16, right: 16 })
          .stateEffect(false)
          .onClick(() => {
            this.selectedCategory = category.id;
          });
        });
      }
      .width('auto')
      .marginBottom(20);
    }
    .scrollBar(BarState.Off)
    .width('100%');
  }
}

// 游戏列表(双列网格)
@Builder
GameList() {
  Column({ space: 12 }) {
    // 列表标题
    Row() {
      Text('游戏列表')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .fontColor('#1e293b');
      
      Text(`(${this.filteredGames.length}款游戏)`)
        .fontSize(14)
        .fontColor('#64748b');
    }
    .justifyContent(FlexAlign.SpaceBetween)
    .alignItems(Alignment.Center)
    .marginBottom(12)
    .width('100%');
    
    // 双列网格 - 对应 React Native 的 FlatList numColumns=2
    Grid() {
      ForEach(this.filteredGames, (game) => {
        GridItem() {
          // 游戏卡片
          Button() {
            Column({ space: 12 }) {
              // 图标容器(包含新品标签)
              Stack() {
                Text(game.icon)
                  .fontSize(40)
                  .textAlign(TextAlign.Center)
                  .width('100%');
                
                // 新品标签
                if (game.isNew) {
                  Text('NEW')
                    .fontSize(10)
                    .fontWeight(FontWeight.Bold)
                    .fontColor('#ffffff')
                    .backgroundColor('#ef4444')
                    .padding({ left: 6, right: 6, top: 2, bottom: 2 })
                    .borderRadius(10)
                    .position({ x: this.screenWidth * 0.5 - 40, y: -6 });
                }
              }
              .width('100%')
              .marginBottom(12);
              
              // 游戏信息
              Column({ space: 8 }) {
                // 标题和评分
                Row() {
                  Text(game.title)
                    .fontSize(16)
                    .fontWeight(FontWeight.Bold)
                    .fontColor('#1e293b')
                    .flex(1);
                  
                  Row({ space: 2 }) {
                    Text(game.rating.toString())
                      .fontSize(12)
                      .fontColor('#64748b');
                    Text('⭐')
                      .fontSize(14);
                  }
                }
                .justifyContent(FlexAlign.SpaceBetween)
                .alignItems(Alignment.Center);
                
                // 描述
                Text(game.description)
                  .fontSize(12)
                  .fontColor('#64748b')
                  .lineHeight(16);
                
                // 底部信息
                Row() {
                  Text(game.players)
                    .fontSize(12)
                    .fontColor('#94a3b8');
                  
                  Text(game.category)
                    .fontSize(12)
                    .fontColor('#ffffff')
                    .backgroundColor('#3b82f6')
                    .padding({ left: 6, right: 6, top: 2, bottom: 2 })
                    .borderRadius(6);
                }
                .justifyContent(FlexAlign.SpaceBetween);
              }
              .flex(1);
            }
          }
          .backgroundColor('#ffffff')
          .borderRadius(12)
          .padding(16)
          .width((this.screenWidth - 48) / 2) // 响应式宽度
          .height('100%')
          .cardShadow()
          .stateEffect(false)
          .onClick(() => {
            AlertDialog.show({
              title: '游戏详情',
              message: `正在启动 ${game.title}`,
              confirm: { value: '确定' }
            });
          });
        }
        .width('50%')
        .padding(8); // 列间距
      });
    }
    .columnsTemplate('1fr 1fr') // 双列布局
    .columnsGap(8) // 列间距
    .rowsGap(16) // 行间距
    .width('100%');
  }
}

// 活动横幅
@Builder
PromoBanner() {
  Column({ space: 4 }) {
    Text('每日挑战活动')
      .fontSize(16)
      .fontWeight(FontWeight.Bold)
      .fontColor('#1e293b');
    
    Text('参与每日挑战,赢取游戏币和道具奖励')
      .fontSize(14)
      .fontColor('#64748b')
      .marginBottom(12);
    
    Button('立即参与')
      .fontSize(14)
      .fontWeight(FontWeight.Bold)
      .fontColor('#ffffff')
      .backgroundColor('#3b82f6')
      .paddingVertical(10)
      .borderRadius(6)
      .width('100%')
      .stateEffect(false);
  }
  .backgroundColor('#dbeafe')
  .borderRadius(12)
  .padding(16)
  .marginTop(16)
  .marginBottom(20)
  .width('100%');
}
(1)横向滚动适配

React Native 的 ScrollView horizontal 转换为鸿蒙的 Scroll({ scrollDirection: ScrollDirection.Horizontal })

// React Native
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
  {featuredGames.map(game => (...))}
</ScrollView>

// 鸿蒙
Scroll({ scrollDirection: ScrollDirection.Horizontal }) {
  Row({ space: 12 }) {
    ForEach(this.featuredGames, (game) => (...));
  }
  .width('auto'); // 关键:设置为auto以实现横向滚动
}
.scrollBar(BarState.Off); // 隐藏滚动条
(2)双列网格布局

React Native 的 FlatList numColumns=2 转换为鸿蒙的 Grid + GridItem

// React Native
<FlatList
  data={filteredGames}
  numColumns={2}
  columnWrapperStyle={styles.columnWrapper}
/>

// 鸿蒙
Grid() {
  ForEach(this.filteredGames, (game) => {
    GridItem() {
      // 游戏卡片
    }
    .width('50%');
  });
}
.columnsTemplate('1fr 1fr') // 双列等宽
.columnsGap(8) // 列间距
.rowsGap(16); // 行间距
(3)计算属性

React Native 的惰性计算变量转换为鸿蒙的 getter 方法:

// React Native
const filteredGames = selectedCategory === 'all' 
  ? games 
  : games.filter(game => game.category === selectedCategory);

// 鸿蒙
private get filteredGames(): Game[] {
  return this.selectedCategory === 'all' 
    ? this.games 
    : this.games.filter(game => game.category === this.selectedCategory);
}
(4)绝对定位

React Native 的 position: 'absolute' 转换为鸿蒙的 position() 方法:

// React Native
newTag: {
  position: 'absolute',
  top: -6,
  right: -6,
}

// 鸿蒙
Text('NEW')
  .position({ x: this.screenWidth * 0.5 - 40, y: -6 });
(5)响应式尺寸

React Native 的动态尺寸计算转换为鸿蒙的变量计算:

// React Native
gameCard: {
  width: (width - 48) / 2,
},
featuredCard: {
  width: width * 0.6,
}

// 鸿蒙
.width((this.screenWidth - 48) / 2) // 游戏卡片宽度
.width(this.screenWidth * 0.6)     // 推荐卡片宽度

该游戏库应用的跨端适配实践表明,React Native 开发的内容聚合类应用向鸿蒙平台迁移时,可实现极高的代码复用率(核心逻辑 90%+ 复用),列表渲染体系的适配是核心难点,但通过鸿蒙的 ScrollGrid 组件可完美解决。这种适配模式特别适合电商、资讯、游戏等内容聚合类移动端应用的跨端开发,能够在保证开发效率的同时,保持一致的用户体验和视觉效果。


真实演示案例代码:






// app.tsx
import React, { useState } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Dimensions, Alert, FlatList } from 'react-native';

// Base64 图标库
const ICONS_BASE64 = {
  solitaire: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  poker: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  mahjong: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  chess: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  dice: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  back: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  star: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  crown: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
};

const { width, height } = Dimensions.get('window');

// 游戏类型
type Game = {
  id: string;
  title: string;
  description: string;
  players: string;
  category: string;
  icon: string;
  rating: number;
  isNew: boolean;
};

const CardGameApp = () => {
  const [games] = useState<Game[]>([
    { id: '1', title: '纸牌接龙', description: '经典的单人纸牌游戏,考验逻辑思维', players: '1人', category: '纸牌', icon: '♠️', rating: 4.8, isNew: true },
    { id: '2', title: '俄罗斯方块', description: '消除方块的经典游戏', players: '1-2人', category: '益智', icon: '🟦', rating: 4.7, isNew: false },
    { id: '3', title: '斗地主', description: '流行的三人纸牌游戏', players: '3人', category: '纸牌', icon: '🃏', rating: 4.9, isNew: true },
    { id: '4', title: '中国象棋', description: '传统策略棋类游戏', players: '2人', category: '棋类', icon: '帅', rating: 4.6, isNew: false },
    { id: '5', title: '五子棋', description: '简单而富有深度的策略游戏', players: '2人', category: '棋类', icon: '⚫', rating: 4.5, isNew: false },
    { id: '6', title: '扫雷', description: '考验逻辑推理的经典游戏', players: '1人', category: '益智', icon: '💣', rating: 4.4, isNew: false },
    { id: '7', title: '麻将', description: '中国传统麻将游戏', players: '4人', category: '牌类', icon: '🀄', rating: 4.7, isNew: true },
    { id: '8', title: '连连看', description: '匹配相同图案的游戏', players: '1-4人', category: '益智', icon: '🎯', rating: 4.3, isNew: false },
  ]);
  
  const [featuredGames] = useState<Game[]>([
    { id: '1', title: '纸牌接龙', description: '经典的单人纸牌游戏,考验逻辑思维', players: '1人', category: '纸牌', icon: '♠️', rating: 4.8, isNew: true },
    { id: '3', title: '斗地主', description: '流行的三人纸牌游戏', players: '3人', category: '纸牌', icon: '🃏', rating: 4.9, isNew: true },
    { id: '4', title: '中国象棋', description: '传统策略棋类游戏', players: '2人', category: '棋类', icon: '帅', rating: 4.6, isNew: false },
  ]);
  
  const [categories] = useState([
    { id: 'all', name: '全部', icon: '🎲' },
    { id: 'card', name: '纸牌', icon: '♠️' },
    { id: 'chess', name: '棋类', icon: '♔' },
    { id: 'puzzle', name: '益智', icon: '🧠' },
    { id: 'dice', name: '骰子', icon: '🎲' },
  ]);
  
  const [selectedCategory, setSelectedCategory] = useState<string>('all');

  const filteredGames = selectedCategory === 'all' 
    ? games 
    : games.filter(game => game.category === selectedCategory);

  const renderGameCard = ({ item }: { item: Game }) => (
    <TouchableOpacity 
      style={styles.gameCard}
      onPress={() => Alert.alert('游戏详情', `正在启动 ${item.title}`)}
    >
      <View style={styles.gameIconContainer}>
        <Text style={styles.gameIcon}>{item.icon}</Text>
        {item.isNew && <Text style={styles.newTag}>NEW</Text>}
      </View>
      <View style={styles.gameInfo}>
        <View style={styles.gameHeader}>
          <Text style={styles.gameTitle}>{item.title}</Text>
          <View style={styles.ratingContainer}>
            <Text style={styles.ratingText}>{item.rating}</Text>
            <Text style={styles.starIcon}></Text>
          </View>
        </View>
        <Text style={styles.gameDescription}>{item.description}</Text>
        <View style={styles.gameFooter}>
          <Text style={styles.playerCount}>{item.players}</Text>
          <Text style={styles.categoryTag}>{item.category}</Text>
        </View>
      </View>
    </TouchableOpacity>
  );

  return (
    <SafeAreaView style={styles.container}>
      {/* 头部 */}
      <View style={styles.header}>
        <Text style={styles.title}>万能游戏库</Text>
        <TouchableOpacity onPress={() => Alert.alert('搜索', '打开搜索功能')}>
          <Text style={styles.headerIcon}>🔍</Text>
        </TouchableOpacity>
      </View>

      <ScrollView style={styles.content}>
        {/* 搜索栏 */}
        <View style={styles.searchContainer}>
          <Text style={styles.searchIcon}>🔍</Text>
          <Text style={styles.searchPlaceholder}>搜索游戏或关键词</Text>
        </View>

        {/* 特色游戏轮播 */}
        <Text style={styles.sectionTitle}>特色推荐</Text>
        <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.featuredContainer}>
          {featuredGames.map(game => (
            <TouchableOpacity 
              key={`featured-${game.id}`} 
              style={styles.featuredCard}
              onPress={() => Alert.alert('特色游戏', `正在启动 ${game.title}`)}
            >
              <View style={styles.featuredIcon}>
                <Text style={styles.featuredIconText}>{game.icon}</Text>
              </View>
              <Text style={styles.featuredTitle}>{game.title}</Text>
              <Text style={styles.featuredRating}>{game.rating}</Text>
            </TouchableOpacity>
          ))}
        </ScrollView>

        {/* 分类导航 */}
        <Text style={styles.sectionTitle}>游戏分类</Text>
        <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.categoryContainer}>
          {categories.map(category => (
            <TouchableOpacity
              key={category.id}
              style={[
                styles.categoryItem,
                selectedCategory === category.id && styles.selectedCategory
              ]}
              onPress={() => setSelectedCategory(category.id)}
            >
              <Text style={[
                styles.categoryIcon,
                selectedCategory === category.id && styles.selectedCategoryIcon
              ]}>
                {category.icon}
              </Text>
              <Text style={[
                styles.categoryText,
                selectedCategory === category.id && styles.selectedCategoryText
              ]}>
                {category.name}
              </Text>
            </TouchableOpacity>
          ))}
        </ScrollView>

        {/* 游戏列表标题 */}
        <View style={styles.listHeader}>
          <Text style={styles.listTitle}>游戏列表</Text>
          <Text style={styles.gameCount}>({filteredGames.length}款游戏)</Text>
        </View>

        {/* 游戏列表 */}
        <FlatList
          data={filteredGames}
          renderItem={renderGameCard}
          keyExtractor={item => item.id}
          showsVerticalScrollIndicator={false}
          numColumns={2}
          columnWrapperStyle={styles.columnWrapper}
        />

        {/* 活动横幅 */}
        <View style={styles.promoBanner}>
          <Text style={styles.promoTitle}>每日挑战活动</Text>
          <Text style={styles.promoDescription}>参与每日挑战,赢取游戏币和道具奖励</Text>
          <TouchableOpacity style={styles.promoButton}>
            <Text style={styles.promoButtonText}>立即参与</Text>
          </TouchableOpacity>
        </View>
      </ScrollView>

      {/* 底部导航 */}
      <View style={styles.bottomNav}>
        <TouchableOpacity style={styles.navItem}>
          <Text style={styles.navIcon}>🎮</Text>
          <Text style={styles.navText}>游戏</Text>
        </TouchableOpacity>
        
        <TouchableOpacity style={styles.navItem}>
          <Text style={styles.navIcon}>🏆</Text>
          <Text style={styles.navText}>排行</Text>
        </TouchableOpacity>
        
        <TouchableOpacity style={styles.navItem}>
          <Text style={styles.navIcon}>🎁</Text>
          <Text style={styles.navText}>商城</Text>
        </TouchableOpacity>
        
        <TouchableOpacity style={styles.navItem}>
          <Text style={styles.navIcon}>👤</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',
  },
  headerIcon: {
    fontSize: 20,
    color: '#64748b',
  },
  content: {
    flex: 1,
    padding: 16,
  },
  searchContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#ffffff',
    borderRadius: 20,
    paddingVertical: 12,
    paddingHorizontal: 16,
    marginBottom: 16,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  searchIcon: {
    fontSize: 18,
    color: '#94a3b8',
    marginRight: 12,
  },
  searchPlaceholder: {
    fontSize: 14,
    color: '#94a3b8',
    flex: 1,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#1e293b',
    marginVertical: 12,
  },
  featuredContainer: {
    marginBottom: 16,
  },
  featuredCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginRight: 12,
    width: width * 0.6,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  featuredIcon: {
    width: 50,
    height: 50,
    borderRadius: 25,
    backgroundColor: '#f1f5f9',
    alignItems: 'center',
    justifyContent: 'center',
    marginBottom: 12,
  },
  featuredIconText: {
    fontSize: 24,
  },
  featuredTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 4,
  },
  featuredRating: {
    fontSize: 14,
    color: '#64748b',
    flexDirection: 'row',
    alignItems: 'center',
  },
  categoryContainer: {
    marginBottom: 20,
  },
  categoryItem: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#f1f5f9',
    borderRadius: 20,
    paddingVertical: 10,
    paddingHorizontal: 16,
    marginRight: 12,
  },
  selectedCategory: {
    backgroundColor: '#3b82f6',
  },
  categoryIcon: {
    fontSize: 18,
    marginRight: 6,
    color: '#64748b',
  },
  selectedCategoryIcon: {
    color: '#ffffff',
  },
  categoryText: {
    fontSize: 14,
    color: '#64748b',
  },
  selectedCategoryText: {
    color: '#ffffff',
  },
  listHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 12,
  },
  listTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#1e293b',
  },
  gameCount: {
    fontSize: 14,
    color: '#64748b',
  },
  columnWrapper: {
    justifyContent: 'space-between',
  },
  gameCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 16,
    width: (width - 48) / 2,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  gameIconContainer: {
    position: 'relative',
    marginBottom: 12,
  },
  gameIcon: {
    fontSize: 40,
    textAlign: 'center',
  },
  newTag: {
    position: 'absolute',
    top: -6,
    right: -6,
    backgroundColor: '#ef4444',
    color: '#ffffff',
    fontSize: 10,
    fontWeight: 'bold',
    paddingHorizontal: 6,
    paddingVertical: 2,
    borderRadius: 10,
  },
  gameInfo: {
    flex: 1,
  },
  gameHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 8,
  },
  gameTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
    flex: 1,
  },
  ratingContainer: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  ratingText: {
    fontSize: 12,
    color: '#64748b',
    marginRight: 2,
  },
  starIcon: {
    fontSize: 14,
  },
  gameDescription: {
    fontSize: 12,
    color: '#64748b',
    lineHeight: 16,
    marginBottom: 12,
  },
  gameFooter: {
    flexDirection: 'row',
    justifyContent: 'space-between',
  },
  playerCount: {
    fontSize: 12,
    color: '#94a3b8',
  },
  categoryTag: {
    fontSize: 12,
    color: '#ffffff',
    backgroundColor: '#3b82f6',
    paddingHorizontal: 6,
    paddingVertical: 2,
    borderRadius: 6,
  },
  promoBanner: {
    backgroundColor: '#dbeafe',
    borderRadius: 12,
    padding: 16,
    marginTop: 16,
    marginBottom: 20,
  },
  promoTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 4,
  },
  promoDescription: {
    fontSize: 14,
    color: '#64748b',
    marginBottom: 12,
  },
  promoButton: {
    backgroundColor: '#3b82f6',
    paddingVertical: 10,
    borderRadius: 6,
    alignItems: 'center',
  },
  promoButtonText: {
    color: '#ffffff',
    fontWeight: 'bold',
  },
  bottomNav: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    backgroundColor: '#ffffff',
    borderTopWidth: 1,
    borderTopColor: '#e2e8f0',
    paddingVertical: 12,
  },
  navItem: {
    alignItems: 'center',
    flex: 1,
  },
  navIcon: {
    fontSize: 20,
    color: '#94a3b8',
    marginBottom: 4,
  },
  navText: {
    fontSize: 12,
    color: '#94a3b8',
  },
});

export default CardGameApp;

请添加图片描述


打包

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

在这里插入图片描述

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

在这里插入图片描述

最后运行效果图如下显示:

请添加图片描述
本文探讨了React Native游戏应用的架构设计与实现策略。采用组件化架构将应用划分为主应用组件、游戏卡片、特色游戏、搜索和分类筛选等功能模块,实现关注点分离。通过useState钩子管理游戏数据、分类状态等,结合TypeScript类型定义确保数据安全。界面采用层次化布局,包含头部、搜索、特色推荐、分类筛选和游戏列表等区域。针对跨平台兼容性,使用React Native核心组件、统一样式定义和平台无关图标等策略。文章还提出了性能优化建议,包括使用FlatList替代ScrollView、采用useReducer管理复杂状态以及添加动画效果提升用户体验。该实现展示了React Native在游戏类应用开发中的可行性和优势。

欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐