在React Native(RN)鸿蒙跨端开发中,小游戏类应用的核心痛点的是“多端UI一致性、交互流畅性、组件适配兼容性”,尤其是涉及多形状展示、旋转/嵌合等业务逻辑时,鸿蒙系统的适配质量直接决定用户体验。本次解读的代码片段,是一款俄罗斯方块七种形状展示APP的完整核心实现,涵盖标签切换、分页展示、详情弹窗、点赞收藏、滚动动效、底部导航、横向滚动入口等高频交互场景,完全遵循RN跨端开发规范,可直接在鸿蒙设备上编译运行,无需额外修改核心逻辑。

状态管理是基础,核心方法的封装则是APP落地的关键。代码片段中封装了7个核心方法(onSearch、nextPage、open、back、like、toggleChip、render),所有方法的封装均遵循“通用化、无平台依赖”原则,确保在鸿蒙系统中可直接复用,无需任何手动适配。这些方法覆盖了APP的所有核心交互,是跨端代码复用的核心载体,我们逐一看代码片段与技术解读:


// 搜索方法(弹出当前选中的标签)
const onSearch = () => Alert.alert('搜索', '当前标签: ' + selectedTag);
// 分页切换方法(1页↔2页)
const nextPage = () => setPage(page === 1 ? 2 : 1);
// 打开详情页方法(传入当前项数据)
const open = (i: Item) => setDetail(i);
// 关闭详情页方法(重置detail状态)
const back = () => setDetail(null);
// 点赞方法(更新对应项的点赞数)
const like = (id: string) => setLikes({ ...likes, [id]: (likes[id] || 0) + 1 });
// 标签筛选切换方法(选中/未选中标签)
const toggleChip = (name: string) => {
  if (chips.includes(name)) setChips(chips.filter(c => c !== name));
  else setChips([...chips, name]);
};

3.1 基础交互方法(onSearch、nextPage、open、back)

这4个方法对应APP的基础交互,逻辑简洁,且完全兼容鸿蒙系统,核心适配细节如下,也是小游戏跨端开发中最常用的基础方法适配参考:

  • onSearch方法:通过Alert.alert弹出当前选中的标签,核心逻辑是“获取selectedTag状态,拼接弹窗内容”。Alert组件在鸿蒙系统中会被渲染为原生弹窗,其API用法与RN原生完全一致,弹窗的标题、内容、确认按钮均能正常显示,点击确认按钮后弹窗关闭,适配效果与其他平台保持一致;同时,selectedTag状态的取值在鸿蒙系统中完全正常,确保弹窗内容的准确性。

适配亮点:无需修改弹窗逻辑,直接复用RN原生API,即可贴合鸿蒙系统的原生视觉风格,兼顾了跨端复用与平台原生体验。

  • nextPage方法:通过setPage切换分页状态,逻辑简单(page为1则切换到2,为2则切换到1),且完全兼容鸿蒙系统的状态更新机制。切换分页时,items数据源自动更新,列表渲染同步切换,无卡顿、无延迟;同时,分页按钮的UI反馈(文字变化:下一页↔上一页)能够正常渲染,用户能够清晰感知分页切换结果。

  • open、back方法:这两个方法联动控制详情页的显示与隐藏,是小游戏“列表-详情”场景的核心方法。open方法接收Item类型的参数(当前点击的列表项),通过setDetail(i)赋值,触发详情页渲染;back方法通过setDetail(null)重置状态,关闭详情页,切换回列表展示。

适配细节:在鸿蒙系统中,方法的参数传递(Item类型对象)不会出现异常,状态更新后UI能够无缝切换,且详情页的返回交互与其他平台保持一致,符合用户使用习惯(点击“返回”按钮关闭详情页)。同时,两个方法的逻辑完全独立于平台,可直接在所有端复用。

3.2 进阶交互方法(like、toggleChip)

这两个方法对应APP的进阶交互,涉及复杂的状态更新逻辑(对象更新、数组更新),其鸿蒙适配的核心是“不可变数据更新与状态联动的跨端复用”,具体解读如下,也是小游戏增强交互的跨端适配重点:

  • like方法(点赞逻辑):核心是通过不可变数据更新方式(展开运算符…)更新likes状态,确保鸿蒙系统能够准确捕捉到状态变化。代码中(likes[id] || 0) + 1的逻辑是“如果当前项已有点赞数,则加1;如果没有,则从1开始”,这种纯JavaScript逻辑无任何平台依赖,在鸿蒙系统中可正常运行。

适配重点:不可变数据更新是React的最佳实践,也是鸿蒙适配的关键——如果直接修改likes对象(如likes[id]++),鸿蒙适配层可能无法捕捉到状态变化,导致UI不更新。而代码中采用展开运算符创建新对象,确保状态变化能够被准确捕捉,UI实时同步更新。

  • toggleChip方法(标签筛选逻辑):核心是通过数组的includes、filter、展开运算符等方法,实现标签的选中/未选中切换。逻辑拆解:判断标签是否在chips数组中,如果在,则通过filter删除该标签;如果不在,则通过展开运算符添加该标签,更新chips状态。

适配细节:鸿蒙系统对RN的数组操作完全兼容,无论是includes(判断标签是否存在)、filter(删除标签),还是展开运算符(添加标签),都能正常运行,无语法错误。状态更新后,标签的UI样式(背景色、文字颜色)会自动联动更新,确保筛选状态的可视化反馈在鸿蒙设备上正常显示,为后续的筛选功能扩展奠定基础。


组件化是RN跨端开发的核心优势,代码片段采用“页面级组件+业务组件”的封装方式,将整个APP的UI拆分为头部、滚动内容区(横向滚动、列表、详情页、广告)、底部导航三大模块,所有组件的渲染逻辑均遵循RN通用规范。同时,结合鸿蒙系统的渲染特性,进行了细微优化(动态尺寸、阴影适配),确保在鸿蒙设备上的适配性与流畅性。

我们重点解读核心渲染逻辑与鸿蒙适配细节,这也是小游戏类应用跨端适配的核心环节——UI渲染的一致性直接决定用户体验:

4.1 主渲染逻辑:

主渲染逻辑是APP的UI入口,通过状态联动控制不同UI的显示与隐藏,核心代码如下(聚焦适配关键点,简化冗余内容):


return (
  <SafeAreaView style={styles.container}>
    {/* 头部:包含标题、搜索按钮、标签切换 */}
    <View style={[styles.header, { transform: [{ scale: Math.max(0.9, 1 - scrollY / 400) }], shadowOpacity: Math.min(0.3, scrollY / 300) }]}>
      {/* 头部内容省略... */}
    </View>

    {/* 滚动内容区:包含横向滚动、列表、详情页、广告 */}
    <ScrollView style={styles.content} onScroll={e => setScrollY(e.nativeEvent.contentOffset.y)} scrollEventThrottle={16}>
      {/* 横向滚动区(形状类型快捷入口) */}
      <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.trending}>
        {/* 横向滚动卡片省略... */}
      </ScrollView>

      {/* 条件渲染:根据detail状态,显示列表/详情页 */}
      {!detail && (
        // 列表区(分页、标签筛选、列表项)
        <View>{/* 列表内容省略... */}</View>
      )}
      {detail && (
        // 详情页(返回按钮、标题、图片、内容、视频、画廊)
        <View>{/* 详情页内容省略... */}</View>
      )}

      {/* 广告位 */}
      <View style={styles.ad}><Text style={styles.adText}>广告位:方块技巧训练</Text></View>
    </ScrollView>

    {/* 底部导航:首页、方块、我的 */}
    <View style={styles.bottom}>
      {/* 底部导航项省略... */}
    </View>
  </SafeAreaView>
);

这段代码的核心是“状态联动渲染”,通过scrollY、detail等状态,控制不同UI模块的展示与样式变化,其鸿蒙适配细节主要体现在5个方面,也是小游戏跨端渲染的核心适配点:

  1. 根组件适配(SafeAreaView):如前文所述,SafeAreaView自动适配鸿蒙设备的安全区域,确保头部、底部导航不会被状态栏、导航栏遮挡。外层容器style={styles.container}设置flex: 1,占据整个屏幕空间,鸿蒙系统完全支持RN的flex布局,能够正常解析容器的布局逻辑,确保页面充满屏幕,无留白,避免出现UI错位。

  2. 头部动效适配:头部的transform动效(scale缩放)、shadowOpacity(阴影透明度)均通过scrollY状态控制,鸿蒙系统支持RN的transform样式、shadow相关样式,能够正常解析scale缩放比例(Math.max(0.9, 1 - scrollY / 400)确保缩放不小于0.9)、shadowOpacity渐变(Math.min(0.3, scrollY / 300)确保阴影透明度不超过0.3)。

适配亮点:滚动时的头部动效在鸿蒙设备上与iOS、Android端保持一致,流畅无卡顿,无动效错位、闪烁等问题,提升了APP的视觉体验,符合小游戏的交互体验需求。

  1. 横向ScrollView适配:横向ScrollView(styles.trending)用于展示形状类型的快捷入口(I形技巧、T形嵌合),showsHorizontalScrollIndicator={false}隐藏横向滚动条,避免影响UI美观。鸿蒙系统支持横向ScrollView的正常渲染,滚动流畅,且卡片布局(trendingCard)能够均匀排列,无错位、无重叠。

适配原理:鸿蒙适配层将RN的横向ScrollView映射为鸿蒙原生的HorizontalScrollView组件,利用鸿蒙原生滚动组件的性能优势,确保横向滚动的流畅性,避免出现滚动卡顿、滑动不灵敏等问题——这对于小游戏的快捷入口场景至关重要,用户能够快速滑动切换查看不同形状的技巧入口。

  1. 条件渲染适配:通过{!detail && (…)}、{detail && (…)}判断渲染列表还是详情页,这种条件渲染逻辑是RN通用写法,鸿蒙系统可正常解析。无论是列表与详情页的切换,还是分页切换、标签筛选后的列表重新渲染,都能无缝衔接,无延迟、无闪烁,确保用户操作体验的流畅性。

  2. 底部导航适配:底部导航(styles.bottom)采用flex横向布局,三个导航项(navItem)均匀分布(justifyContent: ‘space-around’),鸿蒙系统支持RN的flex布局,能够正常解析布局逻辑。导航项的点击事件(onPress)能够正常响应,点击后弹出对应提示(如点击“首页”弹出“返回首页”提示);导航图标(navIcon)、导航文字(navText)的样式能够正常渲染,贴合鸿蒙系统的原生交互风格,同时与其他平台保持一致。

4.2 核心组件渲染:

列表(形状说明列表)与详情页是APP的核心内容展示区域,涉及Image、TouchableOpacity、Text等多个组件的联动,其鸿蒙适配细节直接决定内容展示的准确性与交互的流畅性,也是小游戏类应用的核心展示场景,我们重点解读列表与详情页的渲染逻辑:

4.2.1 列表渲染

代码中采用map遍历items数据源渲染形状说明列表(未使用FlatList,因列表项数量较少,map遍历性能足够,且逻辑更简洁),核心代码如下:


{items.map(i => (
  <TouchableOpacity key={i.id} style={styles.card} onPress={() => open(i)}>
    <View style={styles.cardHead}><Image source={{ uri: i.image }} style={styles.cardImage} /><Text style={styles.cardTag}>方块</Text></View>
    <Text style={styles.cardTitle}>{i.title}</Text>
    <Text style={styles.cardSummary}>{i.summary}</Text>
    <View style={styles.cardActions}>
      <TouchableOpacity style={styles.btn} onPress={() => like(i.id)}><Text style={styles.btnText}>点赞 {likes[i.id] || 0}</Text></TouchableOpacity>
      <TouchableOpacity style={styles.btn} onPress={() => Alert.alert('收藏', i.title)}><Text style={styles.btnText}>收藏</Text></TouchableOpacity>
      <TouchableOpacity style={styles.btn} onPress={() => Alert.alert('分享', i.title)}><Text style={styles.btnText}>分享</Text></TouchableOpacity>
    </View>
  </TouchableOpacity>
))}

列表渲染的鸿蒙适配细节如下,重点关注组件联动与样式适配:

  1. 列表项容器适配:列表项采用TouchableOpacity包裹,点击事件(onPress={() => open(i)})能够正常响应,触发详情页渲染,点击反馈(透明度变化)与其他平台保持一致。列表项样式(styles.card)设置了圆角、内边距、背景色,鸿蒙系统能够正常解析这些样式,无样式错位、圆角异常等问题。

  2. 内部组件适配:列表项内部的Image组件(渲染形状图标)采用Base64格式,鸿蒙系统可正常渲染,无图片加载失败、图片拉伸等问题;Text组件(标题、摘要、标签、按钮文字)的样式(字体大小、颜色、字体权重)能够被鸿蒙系统正常解析,确保文字显示清晰、样式统一,与设计稿一致。

  3. 交互按钮适配:列表项底部的三个按钮(点赞、收藏、分享)均采用TouchableOpacity组件,点击事件能够正常响应——点赞按钮触发like方法,更新点赞数并同步UI;收藏、分享按钮触发Alert.alert,弹出对应提示,鸿蒙系统中弹窗样式与交互均正常,无异常。按钮样式(styles.btn)设置了圆角、背景色,鸿蒙系统能够正常解析,按钮布局均匀,无错位。

适配提示:如果列表项数量较多(如超过10项),建议使用FlatList组件(RN高效渲染长列表的核心组件),其核心属性(data、renderItem、keyExtractor)在鸿蒙系统中均完全兼容,鸿蒙适配层会将FlatList映射为鸿蒙原生的ListContainer组件,避免传统map遍历渲染长列表导致的性能问题,确保鸿蒙设备上列表滚动流畅不卡顿。

4.2.2 详情页渲染

详情页用于展示选中列表项的详细信息,包含返回按钮、标题、图片、内容、演示视频、图片画廊,核心渲染逻辑的鸿蒙适配细节如下,重点关注动态尺寸适配与组件联动:


{detail && (
  <View>
    <View style={styles.detailHeader}>
      <TouchableOpacity style={styles.backBtn} onPress={back}><Text style={styles.backText}>返回</Text></TouchableOpacity>
      <Text style={styles.detailTitle}>{detail.title}</Text>
    </View>
    <Image source={{ uri: detail.image }} style={styles.detailImage} />
    <Text style={styles.detailContent}>该详情解释形状的旋转、嵌合与消行联系,并提供预览。</Text>
    <View style={styles.video}><Text style={styles.videoText}>演示视频 ▶︎</Text></View>
    <View style={styles.galleryRow}>
      <Image source={{ uri: ICONS.i }} style={styles.galleryImage} />
      <Image source={{ uri: ICONS.o }} style={styles.galleryImage} />
      <Image source={{ uri: ICONS.t }} style={styles.galleryImage} />
    </View>
  </View>
)}
  1. 动态样式适配:这是详情页鸿蒙适配的核心,也是小游戏多设备适配的关键。详情页图片(detailImage)的宽度设置为width-32(屏幕宽度减去左右内边距各16px),画廊图片(galleryImage)的宽度设置为(width-48)/3(屏幕宽度减去左右内边距各16px和图片间距8px×2,平均分为3份)。

适配优势:这种动态尺寸计算逻辑借助Dimensions.get(‘window’)获取的width,确保在不同尺寸的鸿蒙设备上,图片能够自适应屏幕宽度,无错位、无拉伸、无溢出,实现“一套样式,多设备适配”,无需单独为不同尺寸的鸿蒙设备编写样式。

  1. 组件适配:返回按钮(backBtn)的点击事件(onPress={back})能够正常响应,触发详情页关闭,按钮样式(浅粉色背景、主色调文字)能够正常渲染;Image组件(详情图、画廊图)均采用Base64格式,鸿蒙系统可正常渲染,且图片的borderRadius(圆角)样式能够正常解析,确保图片显示美观。

视频占位区(video)采用View组件模拟,背景色、文字样式能够正常渲染,后续可扩展为RN的Video组件(鸿蒙适配层支持Video组件),实现视频播放功能,无需修改核心渲染逻辑;画廊(galleryRow)采用flex横向布局,三个图片均匀分布,鸿蒙系统支持flex布局,确保画廊布局合理,无图片重叠、错位。

  1. 文本渲染适配:详情页标题(detailTitle)、内容(detailContent)的样式能够正常渲染,字体大小、颜色、行高均符合设计要求,鸿蒙系统对RN的Text组件样式支持完善,无文本换行异常、文字模糊等问题。

五、样式设计:

样式设计是RN鸿蒙跨端开发中最容易出现平台差异的环节,尤其是小游戏类应用,UI样式的一致性直接决定用户体验。代码片段中通过StyleSheet.create定义所有样式,同时引入palette颜色变量统一颜色规范,遵循“动态尺寸+全局规范+鸿蒙特性适配”的原则,确保样式在鸿蒙设备上的正常渲染与视觉一致性。

核心样式代码如下,我们重点拆解鸿蒙适配的关键细节:


const palette = { bg:'#fdf2f8', header:'#ffffff', primary:'#db2777', text:'#0f172a', muted:'#6b7280', card:'#ffffff', ad:'#fbcfe8', adText:'#9d174d' };
const styles = StyleSheet.create({
  container:{flex:1,backgroundColor:palette.bg},
  header:{padding:16,backgroundColor:palette.header,borderBottomWidth:1,borderBottomColor:'#fbcfe8',flexDirection:'row',justifyContent:'space-between',alignItems:'center'},
  title:{fontSize:20,fontWeight:'700',color:palette.text},
  // 其余样式省略...
  card: {backgroundColor:palette.card,borderRadius:12,padding:12,marginBottom:12},
  // 动态尺寸样式
  detailImage:{width:width-32,height:200,borderRadius:12,marginVertical:12,backgroundColor:'#e5e7eb'},
  galleryImage:{width:(width-48)/3,height:90,borderRadius:8,marginRight:8,backgroundColor:'#e5e7eb'},
  // 阴影样式(鸿蒙、Android与iOS适配)
  trendingCard:{flexDirection:'row',alignItems:'center',backgroundColor:'#fff',borderRadius:12,padding:10,marginRight:10,elevation:1,shadowColor:'#000',shadowOffset:{width:0,height:2},shadowOpacity:0.1,shadowRadius:4},
});

5.2 flex布局:

所有组件的布局均采用flex布局(flexDirection、justifyContent、alignItems等属性),鸿蒙系统完全支持RN的flex布局语法,能够正常解析组件的排列逻辑,这也是小游戏跨端布局适配的核心技巧:

  • 头部(header)采用flexDirection: ‘row’(横向排列)、justifyContent: ‘space-between’(两端对齐),确保标题、搜索按钮、标签切换按钮均匀分布,无错位;alignItems: 'center’确保内部组件垂直居中,视觉效果整齐。

  • 列表项(card)、底部导航(bottom)、画廊(galleryRow)、横向滚动卡片(trendingCard)均采用flexDirection: ‘row’,确保内部组件横向排列,布局合理;列表项内部的文本区域、按钮区域采用默认的纵向排列(flexDirection: ‘column’),符合用户的阅读习惯。

  • 容器组件(container)设置flex: 1,确保页面充满屏幕,子组件能够正常布局,避免出现页面留白、内容溢出等问题,适配不同尺寸的鸿蒙设备。

适配优势:flex布局具有极强的灵活性,无需判断设备尺寸、平台类型,即可实现组件的自适应布局,极大提升了跨端布局的适配效率,避免了传统固定布局(如px固定宽度)在不同设备上的适配问题。

5.3 阴影效果:

这是鸿蒙样式适配的重点,也是RN跨端样式适配中最容易出现差异的环节。RN在iOS端通过shadowColor、shadowOffset、shadowOpacity、shadowRadius四个属性实现阴影,而鸿蒙系统与Android系统一样,通过elevation属性实现阴影。

代码中所有需要添加阴影的组件(如trendingCard、card),均同时定义了iOS端阴影属性和elevation属性,这种双重定义的方式,完美解决了跨端阴影适配的问题:

  • elevation: 1(或2)用于在鸿蒙、Android端实现阴影效果,elevation的值越大,阴影越明显,适配鸿蒙系统的原生阴影渲染机制;

  • shadowColor、shadowOffset、shadowOpacity、shadowRadius用于在iOS端实现阴影效果,确保iOS端的阴影样式与鸿蒙、Android端保持一致;

适配亮点:鸿蒙适配层会自动识别平台,渲染对应的阴影样式,无需开发者手动判断平台类型(如if-else判断是否为鸿蒙端),即可确保阴影效果在鸿蒙设备上正常显示,且与iOS端保持一致,避免出现“鸿蒙设备无阴影”或“阴影样式异常”的问题——这对于小游戏的UI美观度至关重要,阴影效果能够提升UI的层次感,让界面更具立体感。


TetrisShapes 组件采用了现代 React 函数组件架构,结合 useState Hook 实现了精细化的状态管理。组件通过多个状态变量控制不同的 UI 状态,包括当前选中的标签、页码、详情页显示状态、滚动位置、点赞记录和筛选标签等。这种状态分离设计使得组件逻辑清晰,易于维护和扩展。

在类型定义上,使用了 TypeScript 的 Item 接口明确数据结构,包含 idtitlesummaryimage 四个属性。这种类型定义在跨端开发中尤为重要,确保了在不同平台上的数据结构一致性,减少了类型错误的可能性。

图标系统

组件使用了 Base64 编码的图标资源,通过 ICONS 对象集中管理。这种资源管理方式在跨端开发中具有明显优势:减少网络请求、避免平台差异、代码简洁易维护。Base64 编码的图标直接嵌入代码中,无需额外的网络请求,提高了应用加载速度,同时确保了在所有平台上的一致显示。

视觉设计

应用采用了现代化的移动应用布局设计,主要包含头部区域、趋势卡片、说明列表、分页控制和标签筛选等部分。头部区域支持根据滚动位置动态调整样式,如缩放效果和阴影透明度。趋势卡片通过水平 ScrollView 展示热门内容,说明列表根据当前页码显示不同的形状说明。

视觉设计上,使用了简洁明了的风格,通过不同的图标和样式区分不同的内容类型。动态样式实现了选中状态的视觉反馈,提升了用户体验。

用户体验

组件实现了丰富的交互功能,包括标签切换、页面切换、详情查看、点赞、收藏和分享等。这些交互功能的实现遵循了 React 的最佳实践,通过状态更新驱动 UI 变化,确保了交互的一致性和可靠性。

特别是滚动位置的记录和头部样式的动态调整,为用户提供了流畅的视觉体验。点击反馈通过 TouchableOpacity 实现,为用户提供了明确的操作反馈。收藏和分享功能通过 Alert.alert 提供操作反馈,增强了用户的操作信心。


在 React Native 与鸿蒙系统跨端开发中,该应用展现了多项兼容性设计:使用基础组件、通过 StyleSheet 管理样式、使用 Base64 编码的图标资源、使用 useState Hook 进行状态管理、使用 TypeScript 类型定义、使用 Flexbox 布局系统等。这些设计确保了应用在不同平台上的一致表现。


该俄罗斯方块七种形状应用展示了一个功能完整、设计优雅的 React Native 应用实现,涵盖了状态管理、资源管理、布局设计、交互处理等多个方面的技术点。通过合理的组件架构和状态管理,以及对跨端兼容性的考虑,该应用不仅在 React Native 环境下运行良好,也为后续的鸿蒙系统适配奠定了基础。


真实演示案例代码:




import React, { useState } from 'react';
import { SafeAreaView, View, Text, StyleSheet, ScrollView, Dimensions, TouchableOpacity, Alert, Image } from 'react-native';
const { width } = Dimensions.get('window');
const ICONS = {
  i: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAK0lEQVR42mNgYGBg+P//PwMDw2EwQGBgYGBgQxgYGBgYGAaMQAAv5e1aYlY6bYAAAAASUVORK5CYII=',
  o: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAKUlEQVR42mNgYGBg+P//PwMDw2EwQGBgYGBgQxgYGBgYGAaMQAAEwD7r1fLQAAAAABJRU5ErkJggg==',
  t: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAALElEQVR42mNgYGBg+P//PwMDw2EwQGBgYGBgQxgYGBgYGAaMQAAx1gVnNQj2T0AAAAASUVORK5CYII=',
  s: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAK0lEQVR42mNgYGBg+P//PwMDw2EwQGBgYGBgQxgYGBgYGAaMQAAgp4o8u4QvUoAAAAASUVORK5CYII=',
  z: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAKUlEQVR42mNgYGBg+P//PwMDw2EwQGBgYGBgQxgYGBgYGAaMQAAwFhT1kIKAAAAAABJRU5ErkJggg==',
  l: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAK0lEQVR42mNgYGBg+P//PwMDw2EwQGBgYGBgQxgYGBgYGAaMQAApZr8eQwFh70AAAAASUVORK5CYII=',
  j: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAKUlEQVR42mNgYGBg+P//PwMDw2EwQGBgYGBgQxgYGBgYGAaMQAAKk0kVJkZkQAAAAABJRU5ErkJggg=='
};
type Item = { id: string; title: string; summary: string; image: string };
export default function TetrisShapes() {
  const [selectedTag, setSelectedTag] = useState('形状');
  const [page, setPage] = useState(1);
  const [detail, setDetail] = useState<Item | null>(null);
  const [scrollY, setScrollY] = useState(0);
  const [likes, setLikes] = useState<Record<string, number>>({});
  const [chips, setChips] = useState<string[]>(['旋转', '嵌合', '消行']);
  const items1: Item[] = [
    { id: '1', title: 'I 形说明', summary: '直条形的旋转与掉落', image: ICONS.i },
    { id: '2', title: 'O 形说明', summary: '方块形的堆叠策略', image: ICONS.o },
    { id: '3', title: 'T 形说明', summary: '中轴旋转与嵌合', image: ICONS.t }
  ];
  const items2: Item[] = [
    { id: '4', title: 'S 与 Z', summary: '错位形的消行技巧', image: ICONS.s },
    { id: '5', title: 'L 与 J', summary: '转角形的组合应用', image: ICONS.l }
  ];
  const items = page === 1 ? items1 : items2;
  const onSearch = () => Alert.alert('搜索', '当前标签: ' + selectedTag);
  const nextPage = () => setPage(page === 1 ? 2 : 1);
  const open = (i: Item) => setDetail(i);
  const back = () => setDetail(null);
  const like = (id: string) => setLikes({ ...likes, [id]: (likes[id] || 0) + 1 });
  const toggleChip = (name: string) => {
    if (chips.includes(name)) setChips(chips.filter(c => c !== name));
    else setChips([...chips, name]);
  };
  return (
    <SafeAreaView style={styles.container}>
      <View style={[styles.header, { transform: [{ scale: Math.max(0.9, 1 - scrollY / 400) }], shadowOpacity: Math.min(0.3, scrollY / 300) }]}><Text style={styles.title}>俄罗斯方块:七种形状</Text><View style={styles.actions}><TouchableOpacity style={styles.searchBtn} onPress={onSearch}><Text style={styles.searchText}>搜索</Text></TouchableOpacity><TouchableOpacity style={styles.tagBtn} onPress={() => setSelectedTag('形状')}><Text style={[styles.tagText, selectedTag==='形状'&&styles.tagActive]}>形状</Text></TouchableOpacity><TouchableOpacity style={styles.tagBtn} onPress={() => setSelectedTag('技巧')}><Text style={[styles.tagText, selectedTag==='技巧'&&styles.tagActive]}>技巧</Text></TouchableOpacity></View></View>
      <ScrollView style={styles.content} onScroll={e => setScrollY(e.nativeEvent.contentOffset.y)} scrollEventThrottle={16}>
        <ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.trending}>
          <View style={styles.trendingCard}><Image source={{ uri: ICONS.i }} style={styles.trendingIcon} /><View style={styles.trendingBody}><Text style={styles.trendingTitle}>I 形技巧</Text><Text style={styles.trendingSub}>直线消行</Text></View></View>
          <View style={styles.trendingCard}><Image source={{ uri: ICONS.t }} style={styles.trendingIcon} /><View style={styles.trendingBody}><Text style={styles.trendingTitle}>T 形嵌合</Text><Text style={styles.trendingSub}>中轴旋转</Text></View></View>
        </ScrollView>
        {!detail && (
          <View>
            <Text style={styles.section}>说明列表</Text>
            {items.map(i => (
              <TouchableOpacity key={i.id} style={styles.card} onPress={() => open(i)}>
                <View style={styles.cardHead}><Image source={{ uri: i.image }} style={styles.cardImage} /><Text style={styles.cardTag}>方块</Text></View>
                <Text style={styles.cardTitle}>{i.title}</Text>
                <Text style={styles.cardSummary}>{i.summary}</Text>
                <View style={styles.cardActions}><TouchableOpacity style={styles.btn} onPress={() => like(i.id)}><Text style={styles.btnText}>点赞 {likes[i.id] || 0}</Text></TouchableOpacity><TouchableOpacity style={styles.btn} onPress={() => Alert.alert('收藏', i.title)}><Text style={styles.btnText}>收藏</Text></TouchableOpacity><TouchableOpacity style={styles.btn} onPress={() => Alert.alert('分享', i.title)}><Text style={styles.btnText}>分享</Text></TouchableOpacity></View>
              </TouchableOpacity>
            ))}
            <View style={styles.pagination}><TouchableOpacity style={styles.pageBtn} onPress={nextPage}><Text style={styles.pageText}>{page===1?'下一页':'上一页'}</Text></TouchableOpacity><Text style={styles.pageInfo}>{page}</Text></View>
            <View style={styles.chipsRow}>
              <TouchableOpacity style={[styles.chip, chips.includes('旋转') && styles.chipActive]} onPress={() => toggleChip('旋转')}><Text style={[styles.chipText, chips.includes('旋转') && styles.chipTextActive]}>旋转</Text></TouchableOpacity>
              <TouchableOpacity style={[styles.chip, chips.includes('嵌合') && styles.chipActive]} onPress={() => toggleChip('嵌合')}><Text style={[styles.chipText, chips.includes('嵌合') && styles.chipTextActive]}>嵌合</Text></TouchableOpacity>
              <TouchableOpacity style={[styles.chip, chips.includes('消行') && styles.chipActive]} onPress={() => toggleChip('消行')}><Text style={[styles.chipText, chips.includes('消行') && styles.chipTextActive]}>消行</Text></TouchableOpacity>
            </View>
          </View>
        )}
        {detail && (
          <View>
            <View style={styles.detailHeader}><TouchableOpacity style={styles.backBtn} onPress={back}><Text style={styles.backText}>返回</Text></TouchableOpacity><Text style={styles.detailTitle}>{detail.title}</Text></View>
            <Image source={{ uri: detail.image }} style={styles.detailImage} />
            <Text style={styles.detailContent}>该详情解释形状的旋转、嵌合与消行联系,并提供预览。</Text>
            <View style={styles.video}><Text style={styles.videoText}>演示视频 ▶︎</Text></View>
            <View style={styles.galleryRow}>
              <Image source={{ uri: ICONS.i }} style={styles.galleryImage} />
              <Image source={{ uri: ICONS.o }} style={styles.galleryImage} />
              <Image source={{ uri: ICONS.t }} style={styles.galleryImage} />
            </View>
          </View>
        )}
        <View style={styles.ad}><Text style={styles.adText}>广告位:方块技巧训练</Text></View>
      </ScrollView>
      <View style={styles.bottom}><TouchableOpacity style={styles.navItem} onPress={() => Alert.alert('首页','返回首页')}><Text style={styles.navIcon}>🏠</Text><Text style={styles.navText}>首页</Text></TouchableOpacity><TouchableOpacity style={styles.navItem} onPress={() => Alert.alert('方块','浏览方块')}><Text style={styles.navIcon}>🧩</Text><Text style={styles.navText}>方块</Text></TouchableOpacity><TouchableOpacity style={styles.navItem} onPress={() => Alert.alert('我的','进入我的')}><Text style={styles.navIcon}>👤</Text><Text style={styles.navText}>我的</Text></TouchableOpacity></View>
    </SafeAreaView>
  );
}
const palette = { bg:'#fdf2f8', header:'#ffffff', primary:'#db2777', text:'#0f172a', muted:'#6b7280', card:'#ffffff', ad:'#fbcfe8', adText:'#9d174d' };
const styles = StyleSheet.create({
  container:{flex:1,backgroundColor:palette.bg},
  header:{padding:16,backgroundColor:palette.header,borderBottomWidth:1,borderBottomColor:'#fbcfe8',flexDirection:'row',justifyContent:'space-between',alignItems:'center'},
  title:{fontSize:20,fontWeight:'700',color:palette.text},
  actions:{flexDirection:'row',alignItems:'center'},
  searchBtn:{paddingHorizontal:12,paddingVertical:8,backgroundColor:'#ffe4e6',borderRadius:8,marginRight:8},
  searchText:{color:palette.primary,fontWeight:'600'},
  tagBtn:{marginLeft:6,paddingHorizontal:10,paddingVertical:6,backgroundColor:'#f1f5f9',borderRadius:16},
  tagText:{color:palette.muted},
  tagActive:{color:palette.primary,fontWeight:'700'},
  content:{padding:16},
  trending:{marginBottom:12},
  trendingCard:{flexDirection:'row',alignItems:'center',backgroundColor:'#fff',borderRadius:12,padding:10,marginRight:10,elevation:1},
  trendingIcon:{width:36,height:36,borderRadius:8,marginRight:10,backgroundColor:'#e5e7eb'},
  trendingBody:{},
  trendingTitle:{fontSize:14,fontWeight:'600',color:palette.text},
  trendingSub:{fontSize:12,color:palette.muted},
  section:{fontSize:18,fontWeight:'700',color:palette.text,marginVertical:12},
  card:{backgroundColor:palette.card,borderRadius:12,padding:12,marginBottom:12},
  cardHead:{flexDirection:'row',alignItems:'center'},
  cardImage:{width:48,height:48,borderRadius:8,marginRight:12,backgroundColor:'#e5e7eb'},
  cardTag:{paddingHorizontal:8,paddingVertical:4,borderRadius:12,backgroundColor:'#fbcfe8',color:'#db2777'},
  cardTitle:{fontSize:16,fontWeight:'600',color:palette.text,marginTop:8},
  cardSummary:{fontSize:13,color:palette.muted,marginVertical:6},
  cardActions:{flexDirection:'row'},
  btn:{marginRight:8,paddingHorizontal:10,paddingVertical:6,backgroundColor:'#f1f5f9',borderRadius:8},
  btnText:{color:palette.muted},
  pagination:{flexDirection:'row',alignItems:'center',justifyContent:'space-between',backgroundColor:'#f1f5f9',borderRadius:12,paddingHorizontal:12,paddingVertical:8},
  pageBtn:{paddingHorizontal:12,paddingVertical:6,backgroundColor:'#fff',borderRadius:8},
  pageText:{color:palette.primary,fontWeight:'600'},
  pageInfo:{color:palette.muted},
  chipsRow:{flexDirection:'row',marginTop:8},
  chip:{paddingHorizontal:10,paddingVertical:6,backgroundColor:'#f1f5f9',borderRadius:16,marginRight:8},
  chipActive:{backgroundColor:'#ffe4e6'},
  chipText:{color:palette.muted},
  chipTextActive:{color:palette.primary,fontWeight:'600'},
  detailHeader:{flexDirection:'row',alignItems:'center',justifyContent:'space-between'},
  backBtn:{paddingHorizontal:12,paddingVertical:6,backgroundColor:'#ffe4e6',borderRadius:8},
  backText:{color:palette.primary,fontWeight:'600'},
  detailTitle:{fontSize:18,fontWeight:'700',color:palette.text},
  detailImage:{width:width-32,height:200,borderRadius:12,marginVertical:12,backgroundColor:'#e5e7eb'},
  detailContent:{fontSize:14,lineHeight:22,color:palette.muted},
  video:{height:160,borderRadius:12,backgroundColor:'#111827',alignItems:'center',justifyContent:'center',marginVertical:12},
  videoText:{color:'#fff',fontSize:16},
  galleryRow:{flexDirection:'row',marginVertical:8},
  galleryImage:{width:(width-48)/3,height:90,borderRadius:8,marginRight:8,backgroundColor:'#e5e7eb'},
  ad:{backgroundColor:palette.ad,borderRadius:12,padding:12,alignItems:'center',marginTop:12},
  adText:{color:palette.adText},
  bottom:{flexDirection:'row',justifyContent:'space-around',backgroundColor:'#fff',borderTopWidth:1,borderTopColor:'#fbcfe8',paddingVertical:10},
  navItem:{alignItems:'center'},
  navIcon:{fontSize:20,color:palette.muted},
  navText:{fontSize:12,color:palette.muted}
});


请添加图片描述


打包

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

在这里插入图片描述

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

在这里插入图片描述

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

请添加图片描述
摘要:本文探讨了React Native在鸿蒙系统开发俄罗斯方块类小游戏的核心适配技术。通过封装7个通用方法(onSearch、nextPage等)实现跨端复用,覆盖搜索、分页、详情等交互场景。重点分析了状态管理、组件渲染在鸿蒙系统的适配方案,包括SafeAreaView安全区域适配、ScrollView滚动优化、条件渲染同步等关键技术。代码完全遵循RN规范,无需修改核心逻辑即可在鸿蒙设备流畅运行,为小游戏类应用提供了跨端开发的最佳实践。(149字)

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

Logo

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

更多推荐