【React Native for OpenHarmony 实战】搞定底部TabBar开发:从0到1踩坑全记录

【React Native for OpenHarmony 实战】搞定底部TabBar开发:从0到1踩坑全记录

【React Native for OpenHarmony 实战】搞定底部TabBar开发:从0到1踩坑全记录

📝 社区引导

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrosspatform.ZEEKLOG.net

🎯 摘要

本文基于React Native for OpenHarmony技术栈,完整记录了6天开发底部TabBar的全流程,包含从项目搭建、布局实现、交互逻辑到状态优化的完整代码,以及开发中遇到的各类错误与解决方案,为开发者提供可落地的实战参考。


📋 开发背景与目标

本次开发是项目的第8-13天,核心任务是为React Native for OpenHarmony跨平台应用新增不少于4个底部选项卡(TabBar),覆盖首页、数据列表、我的中心、设置等核心场景。

最终要实现:

  • 清晰的视觉区分(默认/选中状态)
  • 平滑的页面切换
  • 切换时保留页面状态(如列表滚动位置)
  • 避免重复加载数据
  • 通过开源鸿蒙设备的运行验证

📅 开发全流程拆解

:前期准备与项目搭建

核心动作

  1. 确认4个选项卡的功能边界,准备图标资源(默认/选中两套)
  2. 技术选型:采用react-native-tab-view + react-native-pager-view实现底部TabBar
  3. 搭建项目目录,导入资源并验证引用正常

依赖安装:

npminstall @react-navigation/bottom-tabs @react-navigation/native react-native-screens react-native-safe-area-context 

踩坑记录

  • ❌ 错误1:图标资源命名包含中文,导致编译时资源无法识别
    ✅ 解决:将图标重命名为纯英文+下划线格式(如home_default.png),并放入assets/images目录
  • ❌ 错误2:依赖版本不兼容,导致项目编译失败
    ✅ 解决:确认@react-navigation/bottom-tabs版本与React Native for OpenHarmony版本匹配,使用npm install --legacy-peer-deps解决依赖冲突

:底部TabBar布局搭建

核心动作
配置底部TabBar的基础布局,设置图标与文字的组合展示,适配不同屏幕尺寸。

核心代码

// src/navigation/TabNavigator.tsx import React from 'react'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { Image, Text, View } from 'react-native'; import HomePage from '../pages/HomePage'; import ListPage from '../pages/ListPage'; import MinePage from '../pages/MinePage'; import SettingPage from '../pages/SettingPage'; const Tab = createBottomTabNavigator(); const TabNavigator = () => { return ( <Tab.Navigator screenOptions={({ route }) => ({ tabBarIcon: ({ focused, color, size }) => { let iconName; if (route.name === '首页') { iconName = focused ? require('../assets/images/home_selected.png') : require('../assets/images/home_default.png'); } else if (route.name === '数据') { iconName = focused ? require('../assets/images/list_selected.png') : require('../assets/images/list_default.png'); } else if (route.name === '我的') { iconName = focused ? require('../assets/images/mine_selected.png') : require('../assets/images/mine_default.png'); } else if (route.name === '设置') { iconName = focused ? require('../assets/images/setting_selected.png') : require('../assets/images/setting_default.png'); } return <Image source={iconName} style={{ width: size, height: size }} />; }, tabBarLabel: ({ focused, color }) => { let label; if (route.name === '首页') label = '首页'; else if (route.name === '数据') label = '数据'; else if (route.name === '我的') label = '我的'; else if (route.name === '设置') label = '设置'; return <Text style={{ color: focused ? '#1890FF' : '#666666', fontSize: 12 }}>{label}</Text>; }, tabBarActiveTintColor: '#1890FF', tabBarInactiveTintColor: '#666666', tabBarStyle: { height: 56, paddingBottom: 4, paddingTop: 4, borderTopWidth: 0.5, borderTopColor: '#E5E5E5', }, })} > <Tab.Screen name="首页" component={HomePage} options={{ headerShown: false }} /> <Tab.Screen name="数据" component={ListPage} options={{ headerShown: false }} /> <Tab.Screen name="我的" component={MinePage} options={{ headerShown: false }} /> <Tab.Screen name="设置" component={SettingPage} options={{ headerShown: false }} /> </Tab.Navigator> ); }; export default TabNavigator; 

踩坑记录

  • ❌ 错误1:使用固定像素值适配,导致在不同设备上显示错位
    ✅ 解决:使用react-native-size-matters库的scale函数实现自适应尺寸
  • ❌ 错误2:TabBar图标与文字间距不一致,导致视觉错位
    ✅ 解决:在tabBarIcon中添加统一的marginBottom,并通过预览实时调整

:交互状态实现

核心动作
实现TabBar的选中状态切换,优化点击交互逻辑,避免重复触发。

核心代码

// 优化后的TabBar点击逻辑 tabBarButton: (props) => { const { onPress, children } = props; return ( <TouchableOpacity onPress={() => { // 避免重复点击同一选项卡 if (props.accessibilityState.selected) return; onPress(); }} style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }} > {children} </TouchableOpacity> ); }, 

踩坑记录

  • ❌ 错误1:选中状态图标颜色未动态变化
    ✅ 解决:通过focused参数判断状态,动态绑定图标资源
  • ❌ 错误2:点击同一选项卡时重复触发页面刷新
    ✅ 解决:在tabBarButton中添加判断,仅当未选中时执行点击事件

:页面绑定与切换

核心动作
开发4个选项卡对应的页面,通过createBottomTabNavigator实现页面与TabBar的绑定,优化切换动画。

核心代码(ListPage示例)

// src/pages/ListPage.tsx import React, { useState, useEffect } from 'react'; import { View, Text, FlatList, StyleSheet } from 'react-native'; const ListPage = () => { const [dataList, setDataList] = useState<string[]>([]); const [isFirstLoad, setIsFirstLoad] = useState(true); useEffect(() => { if (isFirstLoad) { loadData(); setIsFirstLoad(false); } }, []); const loadData = () => { // 模拟网络请求 setTimeout(() => { setDataList(Array.from({ length: 20 }, (_, i) => `列表项 ${i + 1}`)); }, 1000); }; return ( <View style={styles.container}> <FlatList data={dataList} renderItem={({ item }) => ( <View style={styles.item}> <Text style={styles.itemText}>{item}</Text> </View> )} keyExtractor={(item, index) => index.toString()} /> </View> ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#FFFFFF', }, item: { padding: 16, borderBottomWidth: 0.5, borderBottomColor: '#F0F0F0', }, itemText: { fontSize: 16, color: '#333333', }, }); export default ListPage; 

踩坑记录

  • ❌ 错误1:页面路由配置错误,导致切换时白屏
    ✅ 解决:检查Tab.Screennameroute.name是否一一对应
  • ❌ 错误2:页面切换时出现闪屏
    ✅ 解决:为所有页面设置统一的背景色,避免透明背景导致的视觉闪烁

:状态保留与性能优化

核心动作
实现列表页面的滚动位置保留,优化数据加载逻辑,避免重复请求。

核心代码

// src/pages/ListPage.tsx 滚动状态保留优化 import React, { useState, useEffect, useRef } from 'react'; import { View, Text, FlatList, StyleSheet } from 'react-native'; const ListPage = () => { const [dataList, setDataList] = useState<string[]>([]); const [isFirstLoad, setIsFirstLoad] = useState(true); const flatListRef = useRef<FlatList>(null); const [scrollOffset, setScrollOffset] = useState(0); useEffect(() => { if (isFirstLoad) { loadData(); setIsFirstLoad(false); } }, []); const loadData = () => { setTimeout(() => { setDataList(Array.from({ length: 20 }, (_, i) => `列表项 ${i + 1}`)); }, 1000); }; const handleScroll = (event: any) => { setScrollOffset(event.nativeEvent.contentOffset.y); }; useEffect(() => { if (flatListRef.current && scrollOffset > 0) { flatListRef.current.scrollToOffset({ offset: scrollOffset, animated: false }); } }, [dataList]); return ( <View style={styles.container}> <FlatList ref={flatListRef} data={dataList} renderItem={({ item }) => ( <View style={styles.item}> <Text style={styles.itemText}>{item}</Text> </View> )} keyExtractor={(item, index) => index.toString()} onScroll={handleScroll} scrollEventThrottle={16} /> </View> ); }; export default ListPage; 

踩坑记录

  • ❌ 错误1:切换选项卡后列表滚动位置丢失
    ✅ 解决:使用useRef保存FlatList实例,通过scrollToOffset恢复滚动位置
  • ❌ 错误2:每次切换选项卡都触发数据请求
    ✅ 解决:添加isFirstLoad标志位,仅在首次进入页面时加载数据

:设备验证与收尾

核心动作

  1. 全功能回归测试(交互/切换/状态/适配)
  2. 打包生成hap文件,安装到开源鸿蒙设备验证
  3. 修复设备专属问题,整理开发文档

踩坑记录

  • ❌ 错误1:打包时未配置权限,导致设备安装失败
    ✅ 解决:在module.json5中添加网络权限等必要配置
  • ❌ 错误2:真实设备上图标模糊
    ✅ 解决:提供多分辨率图标资源(1x/2x/3x),适配不同设备像素密度

📌 关键知识点总结

  1. 组件选型:优先使用@react-navigation/bottom-tabs,适配React Native for OpenHarmony生态
  2. 状态管理:通过useRefuseState结合,实现页面状态持久化
  3. 性能优化:通过标志位控制数据加载时机,避免重复请求
  4. 设备验证:必须在真实鸿蒙设备上测试,模拟器无法覆盖所有场景

📦 代码托管

完整代码已上传至AtomGit:https://atomgit.com/xxx/react-native-oh-tabbar


🧪 运行验证

在这里插入图片描述

Read more

机器人逆运动学——以六自由度机器人为例(详解、易懂,附全部Matlab代码)

机器人逆运动学——以六自由度机器人为例(详解、易懂,附全部Matlab代码)

前言 前面机器人正运动学主要讲关节变量到末端执行器位姿的关系,也就是知道了关节变量与连杆参数就可以利用D-H参数表来表达末端位姿。而逆运动学就是已知末端的位姿与连杆参数,来求得关节变量的过程。本文首先介绍何为逆运动学,再以例子的形式利用D-H参数表与齐次变换矩阵对机器人进行逆解。 **阅读提醒1:在运动学逆解前,需要掌握运动学正解的相关知识,也要掌握一定的矩阵运算规则。(相关知识点有在我之前的文章提到,我也在本文进行了引用,如有需要可以查阅;我对机器人正运动学相关的matlab分析单独发了一篇博客,有需要也可以查阅) **阅读提醒2:下文灰色补充块是用于解释正文的,用来补充正文没讲到的知识或细节。 一、运动学逆解 上面提到,已知末端执行器的位姿来求解这一位姿对应的全部关节变量就是逆解,然而由于机械结构的差异,有些时候一个末端位姿可能对应着不同的反解情况(多解)。逆运动学问题实质就是非线性超越方程组的求解问题,其解法分为两大类(封闭解法和数值解法),本文主要讲封闭解法。 1.【 封闭解法 】概述 封闭解法是指具有解析形式的解法,其计算速度快、效率高,更便于实时控制,具

8卡RTX 5090服务器llama.cpp测试

8 卡 RTX 5090 服务器 完整安装及性能调优指南  8卡RTX 5090服务器 从 NVIDIA驱动安装 → CUDA环境 → llama.cpp编译 → 多GPU测试 的完整、可直接执行流程(基于Ubuntu 22.04 LTS,适配Blackwell架构)。 一、系统与硬件准备(必做) 1.1 系统要求 • 推荐:Ubuntu 22.04 LTS(64位) • 内核:6.8+ HWE内核(5090必须高内核) • 禁用:Nouveau开源驱动(与NVIDIA驱动冲突) 1.2 硬件检查 Bash # 查看8张5090是否被识别 lspci | grep -i nvidia

Matlab Copilot_AI工具箱: 对接DeepSeek/Kimi/GPT/千问/文心一言等多款AI大模型,一站式提升编程效率

Matlab Copilot_AI工具箱: 对接DeepSeek/Kimi/GPT/千问/文心一言等多款AI大模型,一站式提升编程效率

🔥 为什么需要这款工具? * Matlab 2025虽自带Copilot功能,但受地区、许可证的限制,多数用户无法使用; * 在Matlab和ChatGPT、DeepSeek等AI模型之间来回切换操作繁琐,无法实现“所见即所得”的编程体验,且代码报错后的调试繁琐。 这款Matlab Copilot_AI工具箱作为Matlab与多款AI模型的对接载体,支持DeepSeek V3.2(基础/思考版)、Kimi K2、百度文心一言、阿里云通义千问、ChatGPT(百度千帆版)等模型,还支持4种自定义模型配置(可对接百度千帆平台近百种大模型); 工具直接在Matlab内(不限于2025a)运行,无需切换其他软件,支持“一键生成、运行、调试、修复bug、导出”全流程编程辅助,使用成本可控(单模型月均几元即可满足基础使用),且工具箱一次授权终身免费更新。 多款AI模型可选择,还支持四种自定义模型组合。 更新记录 1. 20260123更新至v4.0,更新:

llama.cpp量化模型部署实战:从模型转换到API服务

1. 为什么你需要关注llama.cpp:让大模型在普通电脑上跑起来 如果你对AI大模型感兴趣,肯定听说过动辄需要几十GB显存的“庞然大物”。想在自己的电脑上跑一个7B参数的模型,以前可能得配一张昂贵的专业显卡。但现在,情况不一样了。我今天要跟你聊的 llama.cpp,就是那个能让大模型“瘦身”并飞入寻常百姓家的神奇工具。 简单来说,llama.cpp是一个用C/C++编写的开源项目,它的核心目标只有一个:用最高效的方式,在消费级硬件(比如你的笔记本电脑CPU)上运行大型语言模型。它不像PyTorch那样是个庞大的深度学习框架,它更像一个“推理引擎”,专注于把训练好的模型,以最小的资源消耗跑起来。 我刚开始接触大模型部署时,也被各种复杂的依赖和巨大的资源需求劝退过。直到用了llama.cpp,我才发现,原来在我的MacBook Pro上,也能流畅地和Llama 2这样的模型对话。这背后的功臣,主要就是两点:纯C/C++实现带来的极致性能,以及模型量化技术带来的体积与速度革命。量化这个词听起来有点技术,你可以把它想象成给模型“压缩图片”