rn_for_openharmony狗狗之家app实战-设置实现
设置页面开发要点 本文介绍了React Native设置页面的开发实践,主要包含以下要点: 主题切换功能:通过全局状态管理darkMode变量,使用getTheme函数动态获取颜色配置,实现深色/浅色模式切换 组件结构: 使用Card组件分组设置项 Switch组件实现开关功能 Alert组件处理确认操作 样式处理: 静态样式使用StyleSheet.create优化性能 动态样式通过内联对象实现
案例开源地址:https://atomgit.com/nutpi/rn_openharmony_dogimg
每个人都不一样
有人晚上刷手机喜欢深色模式,有人觉得浅色看着舒服。有人想收到推送提醒,有人嫌烦。
设置页面就是让用户按自己的习惯来。你提供选项,用户自己选。
做设置页面看起来简单,其实要考虑的东西不少:开关怎么做、颜色怎么跟着变、数据怎么存。这篇文章一个个说。
先看导入了什么
import {View, Text, TouchableOpacity, Switch, StyleSheet, Alert} from 'react-native';
从 react-native 导入基础组件。
Switch 是开关组件,就是那种左右滑动的开关,用来切换开/关状态。
Alert 是弹窗组件,用来显示确认对话框,比如"确定要清除缓存吗?"
import {Header, Card} from '../../components';
项目里封装的公共组件。
Card 用来包裹每组设置项,视觉上更整洁。
import {useStore} from '../../hooks';
import {getTheme} from '../../utils/store';
useStore 是自定义 Hook,用来获取全局状态。
getTheme 根据深色模式状态返回对应的颜色配置。
组件开头
export function SettingsPage() {
const {darkMode} = useStore();
从全局状态里拿 darkMode,这是个布尔值,true 表示深色模式开启。
const colors = getTheme(darkMode);
根据当前模式获取颜色配置。
如果 darkMode 是 true,返回深色主题的颜色;否则返回浅色主题的颜色。
后面所有需要颜色的地方都用 colors.xxx,这样切换主题时颜色会自动变。
const {setState} = require('../../utils/store').appStore;
获取 setState 方法,用来更新全局状态。
用 require 而不是 import 是为了避免循环依赖。这不是最优雅的写法,但能用。
更好的做法是把 setState 也放到 useStore 的返回值里。
主题颜色长什么样
看看 store.ts 里的主题配置:
export const theme = {
light: {
primary: '#D2691E',
background: '#f5f5f5',
card: '#fff',
浅色主题的部分配置。
primary 是主题色,巧克力棕,用于按钮、开关等强调元素。
background 是页面背景色,浅灰色。
card 是卡片背景色,白色。
text: '#333',
textSecondary: '#666',
textMuted: '#999',
border: '#f0f0f0',
},
文字和边框的颜色。
text 是主要文字,深灰色,用于标题和重要内容。
textSecondary 是次要文字,中灰色。
textMuted 是辅助文字,浅灰色,用于描述和提示。
border 是边框和分隔线的颜色。
深色主题的配置
dark: {
primary: '#E8A060',
background: '#121212',
card: '#1E1E1E',
深色主题不是简单地把颜色反过来。
主题色用了更亮的橙色(#E8A060),因为在深色背景上,原来的巧克力棕会显得太暗。
背景用 #121212 而不是纯黑。纯黑在 OLED 屏幕上虽然省电,但看起来太"硬"了。这个深灰色是 Material Design 推荐的深色模式背景。
text: '#E0E0E0',
textSecondary: '#B0B0B0',
textMuted: '#808080',
border: '#333',
},
};
文字颜色反过来,用浅色。
但也不是纯白,纯白在深色背景上太刺眼。用浅灰色更舒服。
getTheme 函数
export const getTheme = (darkMode: boolean) => darkMode ? theme.dark : theme.light;
就一行代码,根据 darkMode 返回对应的主题对象。
组件里用 colors.text 这样的方式访问颜色,不用关心当前是什么模式。
这就是抽象的好处:把"当前是什么模式"这个细节藏起来,外面只管用颜色。
页面的整体结构
return (
<View style={[s.container, {backgroundColor: colors.background}]}>
<Header title="设置" />
<View style={s.content}>
...
</View>
</View>
);
最外层是个 View,背景色用 colors.background。
style={[s.container, {backgroundColor: colors.background}]} 是数组样式的写法。
第一个是静态样式,定义在 StyleSheet 里。
第二个是动态样式,根据主题变化。
React Native 会把数组里的样式合并,后面的覆盖前面的。
为什么要分静态和动态
const s = StyleSheet.create({
container: {flex: 1, backgroundColor: '#f5f5f5'},
...
});
StyleSheet.create 会对样式做优化,比如只传 ID 而不是整个对象到原生层。
但它只能处理静态值,不能处理运行时才知道的值(比如 colors.background)。
所以把不变的放 StyleSheet 里,会变的用内联对象。
通知设置区域
<Card>
<Text style={[s.sectionTitle, {color: colors.textMuted}]}>通知设置</Text>
每组设置用 Card 包裹。
sectionTitle 是组标题,用辅助色(textMuted),字号小一些,起到分组的作用。
<View style={[s.item, {borderBottomColor: colors.border}]}>
<View style={s.itemInfo}>
<Text style={[s.itemLabel, {color: colors.text}]}>推送通知</Text>
<Text style={[s.itemDesc, {color: colors.textMuted}]}>接收新内容和活动通知</Text>
</View>
<Switch value={true} trackColor={{true: colors.primary}} />
</View>
</Card>
一个设置项包含:
itemInfo:左边的信息区,包含标签和描述。
Switch:右边的开关。
描述文字告诉用户这个选项是干嘛的,不是所有选项都需要描述,但复杂的选项最好有。
Switch 组件详解
<Switch value={true} trackColor={{true: colors.primary}} />
value 是当前状态,true 表示开启,false 表示关闭。
trackColor 是轨道颜色。{true: colors.primary} 表示开启时用主题色,关闭时用默认颜色。
Switch 是 React Native 内置的组件,在 iOS 和 Android 上都有原生的外观。
iOS 上是圆形滑块,Android 上是 Material Design 风格。
显示设置区域
<Card>
<Text style={[s.sectionTitle, {color: colors.textMuted}]}>显示设置</Text>
<View style={[s.item, {borderBottomColor: colors.border}]}>
<View style={s.itemInfo}>
<Text style={[s.itemLabel, {color: colors.text}]}>自动播放</Text>
<Text style={[s.itemDesc, {color: colors.textMuted}]}>自动播放视频和动图</Text>
</View>
<Switch value={false} trackColor={{true: colors.primary}} />
</View>
自动播放选项,默认关闭。
有些用户流量有限,不想自动播放视频。给他们选择权。
深色模式开关
<View style={[s.item, {borderBottomColor: colors.border}]}>
<View style={s.itemInfo}>
<Text style={[s.itemLabel, {color: colors.text}]}>深色模式</Text>
<Text style={[s.itemDesc, {color: colors.textMuted}]}>使用深色主题</Text>
</View>
<Switch
value={darkMode}
onValueChange={toggleDarkMode}
trackColor={{true: colors.primary}}
/>
</View>
</Card>
这个开关是真正有功能的。
value={darkMode}:绑定全局状态,状态变了开关也跟着变。
onValueChange={toggleDarkMode}:用户切换时调用这个函数。
切换深色模式的函数
const toggleDarkMode = (value: boolean) => {
setState({darkMode: value});
};
onValueChange 会把新的值传进来,true 或 false。
调用 setState 更新全局状态。
然后神奇的事情发生了:因为所有用到 colors 的组件都订阅了状态变化,它们会自动重新渲染,颜色就变了。
这就是响应式的威力。你不用手动去更新每个组件的颜色,改一个状态,所有依赖它的 UI 自动更新。
存储设置区域
<Card>
<Text style={[s.sectionTitle, {color: colors.textMuted}]}>存储</Text>
<TouchableOpacity
style={[s.item, {borderBottomColor: colors.border}]}
onPress={handleClearCache}
>
清除缓存不是开关,是个操作,所以用 TouchableOpacity 而不是 Switch。
点击后执行 handleClearCache 函数。
<View style={s.itemInfo}>
<Text style={[s.itemLabel, {color: colors.text}]}>清除缓存</Text>
<Text style={[s.itemDesc, {color: colors.textMuted}]}>清除图片和数据缓存</Text>
</View>
<Text style={s.arrow}>›</Text>
</TouchableOpacity>
</Card>
右边用箭头 › 而不是开关,提示用户这是可点击的。
箭头是个常见的 UI 约定:有箭头表示点击后会有下一步操作。
清除缓存的处理
const handleClearCache = () => {
Alert.alert('清除缓存', '确定要清除所有缓存数据吗?', [
{text: '取消', style: 'cancel'},
{text: '确定', onPress: () => Alert.alert('提示', '缓存已清除')},
]);
};
清除缓存是个危险操作,用户可能误点,所以要二次确认。
Alert.alert 的参数:
第一个是标题。
第二个是内容。
第三个是按钮数组。
Alert 按钮的配置
{text: '取消', style: 'cancel'},
取消按钮,style: 'cancel' 在 iOS 上会让按钮加粗,表示这是"安全"的选择。
{text: '确定', onPress: () => Alert.alert('提示', '缓存已清除')},
确定按钮,点击后执行 onPress 回调。
这里只是显示个提示,实际项目里应该真的去清除缓存:
onPress: async () => {
await AsyncStorage.clear();
// 或者清除图片缓存
Alert.alert('提示', '缓存已清除');
}
账号设置区域
<Card>
<Text style={[s.sectionTitle, {color: colors.textMuted}]}>账号</Text>
<TouchableOpacity style={[s.item, {borderBottomColor: colors.border}]}>
<View style={s.itemInfo}>
<Text style={[s.itemLabel, {color: colors.text}]}>隐私政策</Text>
</View>
<Text style={s.arrow}>›</Text>
</TouchableOpacity>
隐私政策和用户协议是法律要求的必备项。
没有描述文字,因为标题已经足够说明。
点击后应该跳转到对应的页面或打开网页:
onPress={() => Linking.openURL('https://example.com/privacy')}
<TouchableOpacity style={[s.item, {borderBottomColor: colors.border}]}>
<View style={s.itemInfo}>
<Text style={[s.itemLabel, {color: colors.text}]}>用户协议</Text>
</View>
<Text style={s.arrow}>›</Text>
</TouchableOpacity>
</Card>
用户协议同理。
这两个页面内容通常是法务提供的,开发只需要展示。
设置项的样式
sectionTitle: {fontSize: 14, color: '#999', marginBottom: 12},
组标题用 14 号字,浅灰色。
比正文小,颜色浅,不抢眼但能起到分组作用。
marginBottom: 12 和下面的设置项隔开。
item: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 12,
borderBottomWidth: 1,
borderBottomColor: '#f0f0f0'
},
设置项横向布局(flexDirection: 'row'),垂直居中(alignItems: 'center')。
paddingVertical: 12 上下留白,让点击区域足够大。手指比较粗也能点到。
底部有 1 像素的分隔线。
itemInfo: {flex: 1},
信息区 flex: 1 占据除了开关/箭头之外的所有空间。
itemLabel: {fontSize: 16, color: '#333'},
itemDesc: {fontSize: 13, color: '#999', marginTop: 2},
标签是主要信息,16 号字,深色。
描述是次要信息,13 号字,浅色。
marginTop: 2 让描述和标签之间有点间距。
arrow: {fontSize: 18, color: '#ccc'},
箭头用浅灰色,不抢眼但能看到。
扩展:跟随系统主题
可以让 App 自动跟随系统的深色模式:
import {useColorScheme} from 'react-native';
function App() {
const systemColorScheme = useColorScheme();
useColorScheme 是 React Native 提供的 Hook,返回系统当前的颜色模式。
返回值是 'light'、'dark' 或 null(无法确定时)。
useEffect(() => {
if (followSystem) {
setState({darkMode: systemColorScheme === 'dark'});
}
}, [systemColorScheme, followSystem]);
当系统模式变化时,自动更新 App 的模式。
followSystem 是另一个设置项,让用户选择是手动控制还是跟随系统。
扩展:设置的持久化
用户的设置应该保存下来,下次打开 App 还是同样的配置:
import AsyncStorage from '@react-native-async-storage/async-storage';
// 设置变化时保存
useEffect(() => {
AsyncStorage.setItem('settings', JSON.stringify({
darkMode,
pushNotification,
autoPlay,
}));
}, [darkMode, pushNotification, autoPlay]);
每次设置变化,就保存到本地存储。
JSON.stringify 把对象转成字符串,因为 AsyncStorage 只能存字符串。
// App 启动时读取
useEffect(() => {
AsyncStorage.getItem('settings').then(data => {
if (data) {
const settings = JSON.parse(data);
setState(settings);
}
});
}, []);
App 启动时读取保存的设置,恢复用户的配置。
空依赖数组 [] 表示只在组件挂载时执行一次。
扩展:更多设置项
可以根据需要加更多设置:
语言设置:切换 App 的显示语言,需要配合 i18n 库。
字体大小:让视力不好的用户能调大字体。
图片质量:在流量有限时选择加载低质量图片。
仅 WiFi 加载:移动网络下不自动加载图片。
震动反馈:点击按钮时是否震动。
扩展:数据驱动的设置列表
当设置项很多时,可以用数据驱动的方式:
const SETTINGS = [
{
title: '通知',
items: [
{key: 'push', label: '推送通知', desc: '接收新内容通知', type: 'switch'},
{key: 'sound', label: '提示音', type: 'switch'},
],
},
{
title: '显示',
items: [
{key: 'darkMode', label: '深色模式', type: 'switch'},
{key: 'fontSize', label: '字体大小', type: 'select', options: ['小', '中', '大']},
],
},
];
把设置项定义成数据,然后用 map 渲染。
加新设置只需要往数组里加对象,不用改渲染代码。
这种方式在设置项很多时特别有用,代码更好维护。
扩展:退出登录
如果 App 有登录功能,设置页面通常还有退出登录按钮:
<TouchableOpacity style={s.logoutBtn} onPress={handleLogout}>
<Text style={s.logoutText}>退出登录</Text>
</TouchableOpacity>
退出登录也是危险操作,要二次确认:
const handleLogout = () => {
Alert.alert('退出登录', '确定要退出当前账号吗?', [
{text: '取消', style: 'cancel'},
{text: '退出', style: 'destructive', onPress: doLogout},
]);
};
style: 'destructive' 在 iOS 上会让按钮变成红色,表示这是个危险操作。
小结
设置页面的实现要点:
- Switch 组件用于开关类设置,value 绑定状态,onValueChange 更新状态
- Alert 弹窗用于危险操作的二次确认
- 主题系统定义两套颜色,用 getTheme 获取当前主题
- 动态样式用数组合并静态和动态样式
- 分组展示用 Card 和标题组织设置项
- 持久化用 AsyncStorage 保存用户的设置
设置页面是用户个性化 App 的入口,做好了能提升用户满意度。
下一篇讲关于我们页面,展示 App 和团队的信息。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)