rn_for_openharmony狗狗之家app实战-个人中心实现
案例开源地址:https://atomgit.com/nutpi/rn_openharmony_dogimg
用户的专属空间
个人中心是 App 里用户的"家"。在这里,用户可以看到自己的收藏、调整设置、了解 App 信息。
虽然狗狗之家目前没有登录功能,但个人中心依然重要。它展示用户的使用数据,提供各种功能入口,是 App 不可或缺的一部分。
这篇文章讲 ProfilePage 的实现,涉及到用户信息展示、数据统计、菜单列表等内容。
页面的导入部分
import React from 'react';
import {View, Text, ScrollView, TouchableOpacity, StyleSheet} from 'react-native';
import {Header, Card} from '../../components';
import {useNavigation, useStore} from '../../hooks';
导入了 ScrollView,因为个人中心的内容可能超出一屏。
Card 组件用来包裹菜单列表,提供统一的卡片样式。
useStore 获取全局状态,这里主要是收藏数据。
获取导航和状态
export function ProfilePage() {
const {navigate} = useNavigation();
const {favoriteBreeds, favoriteImages} = useStore();
navigate 用于跳转到各个子页面。
favoriteBreeds 和 favoriteImages 是用户收藏的品种和图片,用于显示统计数字。
这两个数组存在全局状态里,用户在其他页面收藏的内容,这里能实时看到数量变化。
页面整体结构
return (
<View style={s.container}>
<Header title="我的" showBack={false} />
<ScrollView style={s.content}>
<View style={s.userSection}>...</View>
<View style={s.stats}>...</View>
<Card>...</Card>
<Card>...</Card>
</ScrollView>
</View>
);
页面分成几个区域:
Header:顶部导航栏,showBack={false} 因为这是底部 Tab 页面。
userSection:用户信息区,显示头像、昵称、签名。
stats:统计区,显示收藏数量。
Card:菜单列表,分成两组。
用户信息区
<View style={s.userSection}>
<View style={s.avatar}>
<Text style={s.avatarEmoji}>🐕</Text>
</View>
<Text style={s.userName}>狗狗爱好者</Text>
<Text style={s.userDesc}>热爱狗狗,关爱生命</Text>
</View>
没有登录功能,所以用默认的头像和昵称。
头像用 emoji 代替图片,简单直接。🐕 狗狗符号和 App 主题呼应。
昵称"狗狗爱好者"是个通用的称呼,签名"热爱狗狗,关爱生命"传达 App 的理念。
用户信息区样式
userSection: {
backgroundColor: '#D2691E',
padding: 24,
alignItems: 'center'
},
用主题色做背景,让这个区域成为页面的视觉焦点。
padding: 24 给内容足够的呼吸空间。
alignItems: 'center' 让头像、昵称、签名都居中。
avatar: {
width: 70,
height: 70,
borderRadius: 35,
backgroundColor: 'rgba(255,255,255,0.2)',
alignItems: 'center',
justifyContent: 'center',
marginBottom: 10
},
avatarEmoji: {fontSize: 36},
头像是个圆形容器,borderRadius: 35 是宽高的一半,形成正圆。
rgba(255,255,255,0.2) 是半透明白色,在主题色背景上形成柔和的对比。
emoji 用 36 号字,在 70x70 的容器里大小合适。
userName: {fontSize: 18, fontWeight: '600', color: '#fff'},
userDesc: {fontSize: 13, color: '#fff', opacity: 0.8, marginTop: 4},
昵称用白色加粗字体,在深色背景上清晰可见。
签名用小一号的字体,opacity: 0.8 让它比昵称稍暗,形成主次关系。
统计区
<View style={s.stats}>
<TouchableOpacity style={s.statItem} onPress={() => navigate('Favorites')}>
<Text style={s.statValue}>{favoriteBreeds.length}</Text>
<Text style={s.statLabel}>收藏品种</Text>
</TouchableOpacity>
<TouchableOpacity style={s.statItem} onPress={() => navigate('Favorites')}>
<Text style={s.statValue}>{favoriteImages.length}</Text>
<Text style={s.statLabel}>收藏图片</Text>
</TouchableOpacity>
</View>
两个统计项:收藏的品种数和图片数。
用 favoriteBreeds.length 和 favoriteImages.length 获取数组长度,这是实时数据。
点击任意一个都跳转到收藏页面,用户可以查看具体收藏了什么。
统计区样式
stats: {
flexDirection: 'row',
backgroundColor: '#fff',
paddingVertical: 16,
marginBottom: 8
},
statItem: {flex: 1, alignItems: 'center'},
flexDirection: 'row' 让两个统计项横向排列。
flex: 1 让它们平分宽度。
alignItems: 'center' 让数字和标签居中。
白色背景和上面的主题色背景形成对比,视觉上分层。
statValue: {fontSize: 22, fontWeight: 'bold', color: '#D2691E'},
statLabel: {fontSize: 12, color: '#666', marginTop: 4},
数字用主题色和大号加粗字体,是统计区的焦点。
标签用小号灰色字体,说明数字的含义。
菜单列表的数据驱动渲染
<Card>
{[
{icon: '❤️', label: '我的收藏', route: 'Favorites'},
{icon: '⚙️', label: '设置', route: 'Settings'},
].map((item, i) => (
<TouchableOpacity key={i} style={s.menuItem} onPress={() => navigate(item.route)}>
<Text style={s.menuIcon}>{item.icon}</Text>
<Text style={s.menuLabel}>{item.label}</Text>
<Text style={s.menuArrow}>›</Text>
</TouchableOpacity>
))}
</Card>
用数组存储菜单数据,然后用 map 渲染。
这种数据驱动的写法比写多个重复的 TouchableOpacity 更简洁。要加新菜单项,只需要往数组里加一个对象。
每个菜单项有三个属性:icon(emoji 图标)、label(文字标签)、route(跳转路由)。
第二组菜单
<Card>
{[
{icon: '📖', label: '关于我们', route: 'About'},
{icon: '⭐', label: '给个好评', route: 'Rate'},
{icon: '📞', label: '联系我们', route: 'Feedback'},
].map((item, i) => (
<TouchableOpacity key={i} style={s.menuItem} onPress={() => navigate(item.route)}>
<Text style={s.menuIcon}>{item.icon}</Text>
<Text style={s.menuLabel}>{item.label}</Text>
<Text style={s.menuArrow}>›</Text>
</TouchableOpacity>
))}
</Card>
第二组是"关于"类的菜单,和第一组的"功能"类菜单分开。
分组让菜单更有条理,用户能快速找到想要的功能。
两个 Card 之间有间距(Card 组件自带 marginVertical),视觉上分隔两组。
菜单项样式
menuItem: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 14,
borderBottomWidth: 1,
borderBottomColor: '#f0f0f0'
},
flexDirection: 'row' 让图标、标签、箭头横向排列。
paddingVertical: 14 给菜单项足够的点击区域。
底部有条细线分隔,最后一项的线会被 Card 的圆角裁掉,所以不用特殊处理。
menuIcon: {fontSize: 20, marginRight: 12},
menuLabel: {flex: 1, fontSize: 16, color: '#333'},
menuArrow: {fontSize: 18, color: '#ccc'},
图标 20 号字,和标签之间有 12 的间距。
标签 flex: 1 占据中间所有空间,把箭头挤到右边。
箭头用浅灰色,不抢眼但能提示"可点击进入"。
为什么用 emoji 做图标
项目里大量使用 emoji 作为图标,原因有几个:
简单:不需要引入图标库或图片资源。
跨平台:emoji 在 iOS、Android、鸿蒙上都能显示。
表达力强:一个符号就能传达含义,❤️ 是收藏,⚙️ 是设置,一目了然。
体积小:不增加包体积。
当然,emoji 的缺点是样式不可控,不同系统显示效果可能略有差异。如果对视觉一致性要求高,还是用图标库或自定义图片。
页面容器样式
container: {flex: 1, backgroundColor: '#f5f5f5'},
content: {flex: 1},
灰色背景,和其他页面保持一致。
content 的 flex: 1 让 ScrollView 占据 Header 之外的所有空间。
全局状态的来源
收藏数据存在 appStore 里:
export interface AppState {
favoriteBreeds: number[];
favoriteImages: string[];
darkMode: boolean;
}
export const appStore = createStore<AppState>({
favoriteBreeds: [],
favoriteImages: [],
darkMode: false,
});
favoriteBreeds 存品种 ID,是数字数组。
favoriteImages 存图片 ID,是字符串数组(The Dog API 的图片 ID 是字符串)。
darkMode 是深色模式开关,后面的文章会讲。
createStore 的实现
function createStore<T extends object>(initial: T) {
let state = initial;
const listeners = new Set<Listener<T>>();
return {
getState: () => state,
setState: (partial: Partial<T>) => {
state = {...state, ...partial};
listeners.forEach(fn => fn(state));
},
subscribe: (fn: Listener<T>): (() => void) => {
listeners.add(fn);
return () => {
listeners.delete(fn);
};
},
};
}
这是个简单的状态管理实现。
getState 返回当前状态。
setState 接收部分状态,合并到现有状态,然后通知所有订阅者。
subscribe 添加订阅者,返回取消订阅的函数。
Partial<T> 是 TypeScript 的工具类型,让 T 的所有属性变成可选。这样 setState 时不用传完整的状态对象。
useStore Hook 的工作原理
export function useStore(): AppState & {...} {
const [state, setState] = useState(appStore.getState());
useEffect(() => {
const unsubscribe = appStore.subscribe(setState);
return unsubscribe;
}, []);
// ...
return {...state, toggleBreed, toggleImage};
}
组件挂载时订阅状态变化,状态变化时调用 setState 触发重渲染。
组件卸载时取消订阅,避免内存泄漏。
这样任何组件调用 useStore,都能获取最新状态,状态变化时自动更新。
扩展:登录功能
如果要加登录功能,用户信息区会变成:
{user ? (
<View style={s.userSection}>
<Image source={{uri: user.avatar}} style={s.avatarImg} />
<Text style={s.userName}>{user.nickname}</Text>
<Text style={s.userDesc}>{user.bio}</Text>
</View>
) : (
<TouchableOpacity style={s.loginSection} onPress={() => navigate('Login')}>
<Text style={s.loginText}>点击登录</Text>
</TouchableOpacity>
)}
根据是否有 user 数据,显示用户信息或登录入口。
登录后头像变成真实图片,昵称和签名也是用户自己设置的。
扩展:更多统计
可以加更多统计项:
<View style={s.stats}>
<StatItem value={favoriteBreeds.length} label="收藏品种" />
<StatItem value={favoriteImages.length} label="收藏图片" />
<StatItem value={adoptApplications.length} label="领养申请" />
<StatItem value={votedImages.length} label="投票图片" />
</View>
展示用户在 App 里的各种活动数据,增加成就感。
扩展:会员等级
<View style={s.levelSection}>
<Text style={s.levelIcon}>🏆</Text>
<Text style={s.levelName}>狗狗达人</Text>
<View style={s.levelProgress}>
<View style={[s.levelBar, {width: '60%'}]} />
</View>
<Text style={s.levelTip}>再收藏 10 个品种升级为"狗狗专家"</Text>
</View>
根据用户的活跃度设置等级,用进度条显示升级进度。
游戏化的设计能提升用户粘性。
小结
个人中心页面的实现要点:
- 用户信息区:主题色背景,头像居中,形成视觉焦点
- 统计区:展示收藏数量,点击可跳转详情
- 菜单列表:数据驱动渲染,分组展示
- 全局状态:用 createStore 实现简单的状态管理
- emoji 图标:简单跨平台,表达力强
个人中心是用户了解自己使用情况的窗口,设计好了能提升用户的归属感。
下一篇讲我的收藏页面,展示用户收藏的品种和图片。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐




所有评论(0)