rn_for_openharmony商城项目app实战-主题设置实现

案例开源地址:https://atomgit.com/nutpi/rn_openharmony_buy

写在前面

深色模式这几年特别火,iOS 13 和 Android 10 都加入了系统级的深色模式支持。作为一个现代 App,不支持深色模式好像有点说不过去。

我自己是深色模式的重度用户,晚上躺床上刷手机的时候,浅色界面简直亮瞎眼。所以每次装新 App,第一件事就是去设置里找有没有深色模式。如果没有,好感度直接减一半。

这篇文章记录一下主题设置页面的实现过程。页面本身和语言设置差不多,就是一个选择列表。但深色模式的实现比多语言复杂一些,涉及到整个 App 的样式切换。

支持哪些主题

先定义支持的主题选项:

tsx 复制代码
const themes = [
  {id: 'system', name: '跟随系统', icon: '📱', desc: '自动适应系统深浅色模式'},
  {id: 'light', name: '浅色模式', icon: '☀️', desc: '始终使用浅色主题'},
  {id: 'dark', name: '深色模式', icon: '🌙', desc: '始终使用深色主题'},
];

三个选项:

跟随系统:系统是深色模式,App 就是深色;系统是浅色模式,App 就是浅色。这是最省心的选项,大部分用户会选这个。

浅色模式:不管系统是什么模式,App 始终是浅色。有些用户就是喜欢浅色,或者觉得深色模式看着累。

深色模式:不管系统是什么模式,App 始终是深色。晚上用手机的用户会喜欢这个。

每个选项有四个属性:id 是标识符,name 是显示名称,icon 是图标,desc 是描述文字。描述文字能帮助用户理解每个选项的含义。

引入需要的依赖

tsx 复制代码
import React from 'react';

不需要本地状态,主题设置存在全局状态里。

tsx 复制代码
import {View, Text, StyleSheet, TouchableOpacity, Alert} from 'react-native';

和语言设置页面用的组件一样。设置类页面的结构都差不多,可以复用很多代码。

tsx 复制代码
import {useApp} from '../store/AppContext';
import {Header} from '../components/Header';

获取全局状态

tsx 复制代码
export const ThemeScreen = () => {
  const {settings, updateSettings, goBack} = useApp();

和语言设置一样,从全局状态里取 settingsupdateSettingsgoBack

选择主题的逻辑

tsx 复制代码
const handleSelect = (id: string) => {
  updateSettings('theme', id);
  Alert.alert('设置成功', '主题设置已更新', [{text: '确定', onPress: goBack}]);
};

逻辑很简单:更新全局状态里的 theme 字段,弹个提示,返回上一页。

实际项目中,切换主题后应该立即生效,整个 App 的颜色都要变。这需要配合主题系统来实现,后面会详细讲。

页面结构

tsx 复制代码
return (
  <View style={styles.container}>
    <Header title="主题设置" />

    <View style={styles.content}>
      {themes.map(theme => (
        // 主题选项
      ))}
    </View>

    <Text style={styles.tip}>
      💡 深色模式可以在夜间使用时减少眼睛疲劳,同时节省电量
    </Text>
  </View>
);

比语言设置多了一个底部提示,告诉用户深色模式的好处。

渲染主题选项

tsx 复制代码
{themes.map(theme => (
  <TouchableOpacity 
    key={theme.id} 
    style={styles.item} 
    onPress={() => handleSelect(theme.id)}
  >
    <View style={styles.iconWrap}>
      <Text style={styles.icon}>{theme.icon}</Text>
    </View>

左边是图标,用一个圆形背景包裹,看起来更精致。

tsx 复制代码
    <View style={styles.info}>
      <Text style={styles.themeName}>{theme.name}</Text>
      <Text style={styles.themeDesc}>{theme.desc}</Text>
    </View>

中间是主题名称和描述。描述用小号灰色字,补充说明这个选项的作用。

tsx 复制代码
    {settings.theme === theme.id && <Text style={styles.check}>✓</Text>}
  </TouchableOpacity>
))}

右边是选中标记,和语言设置一样的逻辑。

样式定义

typescript 复制代码
const styles = StyleSheet.create({
  container: {flex: 1, backgroundColor: '#f5f5f5'},
  content: {flex: 1, backgroundColor: '#fff', marginTop: 12},

基础布局,灰色背景上放白色列表。

typescript 复制代码
  item: {
    flexDirection: 'row', 
    alignItems: 'center', 
    padding: 16, 
    borderBottomWidth: 1, 
    borderBottomColor: '#f0f0f0'
  },

每个选项横向排列,底部有分割线。

typescript 复制代码
  iconWrap: {
    width: 44, 
    height: 44, 
    borderRadius: 22, 
    backgroundColor: '#f5f5f5', 
    justifyContent: 'center', 
    alignItems: 'center', 
    marginRight: 12
  },
  icon: {fontSize: 24},

图标用圆形灰色背景包裹,44x44 的大小,比语言设置的国旗更精致一些。

typescript 复制代码
  info: {flex: 1},
  themeName: {fontSize: 16, color: '#333', fontWeight: '500'},
  themeDesc: {fontSize: 12, color: '#999', marginTop: 2},

名称用 16 号字加粗,描述用 12 号灰色字。

typescript 复制代码
  check: {fontSize: 20, color: '#3498db', fontWeight: 'bold'},
  tip: {fontSize: 13, color: '#999', padding: 16, lineHeight: 20},
});

底部提示用灰色小字,不要太醒目。

深色模式的实现方案

上面只是做了主题设置的 UI,真正的深色模式实现需要更多工作。聊一下常见的方案:

方案一:React Native 内置的 useColorScheme

React Native 提供了 useColorScheme hook,可以获取系统当前的颜色模式:

tsx 复制代码
import {useColorScheme} from 'react-native';

const colorScheme = useColorScheme(); // 'light' | 'dark' | null

这个 hook 会返回 'light''dark'nullnull 表示系统不支持或者无法确定。

配合我们的主题设置,可以这样判断当前应该用什么主题:

tsx 复制代码
const systemTheme = useColorScheme();
const currentTheme = settings.theme === 'system' 
  ? (systemTheme || 'light') 
  : settings.theme;

如果用户选了"跟随系统",就用系统主题;否则用用户选择的主题。

方案二:styled-components 的 ThemeProvider

如果用 styled-components,可以用 ThemeProvider 来管理主题:

tsx 复制代码
const lightTheme = {
  background: '#ffffff',
  text: '#333333',
  primary: '#3498db',
};

const darkTheme = {
  background: '#1a1a1a',
  text: '#ffffff',
  primary: '#5dade2',
};

<ThemeProvider theme={currentTheme === 'dark' ? darkTheme : lightTheme}>
  <App />
</ThemeProvider>

然后在组件里通过 props.theme 获取当前主题的颜色值。

方案三:React Context 自己实现

不用第三方库,自己用 Context 实现也可以:

tsx 复制代码
const ThemeContext = React.createContext({
  theme: 'light',
  colors: lightColors,
});

// 在组件里使用
const {colors} = useContext(ThemeContext);
<View style={{backgroundColor: colors.background}}>

这个方案灵活性最高,但需要自己处理的东西也最多。

我们这个项目为了简化,没有真正实现深色模式切换,只是保存了主题设置。实际项目中建议用方案一或方案三。

深色模式的设计要点

做深色模式不是简单地把白色换成黑色,有很多细节要注意:

1. 不要用纯黑色

纯黑色(#000000)在 OLED 屏幕上和其他颜色对比太强烈,看着不舒服。建议用深灰色,比如 #1a1a1a 或 #121212。

Google 的 Material Design 推荐深色模式的背景色是 #121212,这个颜色经过了大量测试,视觉效果比较好。

2. 文字颜色要降低对比度

浅色模式下文字是黑色(#333333),深色模式下不要用纯白色(#ffffff),用稍微暗一点的白色(#e0e0e0)。

对比度太高会让眼睛疲劳,特别是在暗环境下。

3. 主题色可能需要调整

有些颜色在浅色背景上好看,在深色背景上可能不好看。比如我们的主题蓝色 #3498db,在深色背景上可能需要调亮一点。

4. 图片和图标

有些图片在深色背景上可能看不清,需要准备深色模式专用的图片。图标如果是深色的,在深色背景上也会看不清。

一个技巧是给图标加个浅色的背景或者边框。

5. 阴影效果

浅色模式下常用的阴影效果,在深色模式下基本看不出来。可以用边框或者微妙的颜色差异来代替。

跟随系统的实现

"跟随系统"选项需要监听系统主题变化:

tsx 复制代码
import {useColorScheme, Appearance} from 'react-native';

// 方式一:用 hook(推荐)
const colorScheme = useColorScheme();

// 方式二:监听变化
useEffect(() => {
  const subscription = Appearance.addChangeListener(({colorScheme}) => {
    console.log('系统主题变了:', colorScheme);
  });
  return () => subscription.remove();
}, []);

useColorScheme 会自动响应系统主题变化,组件会重新渲染。如果需要在变化时执行一些逻辑(比如保存到本地),可以用 Appearance.addChangeListener

主题切换的动画

切换主题时,如果颜色瞬间变化会很突兀。可以加个过渡动画:

tsx 复制代码
// 简单的方案:用 LayoutAnimation
import {LayoutAnimation} from 'react-native';

const handleSelect = (id: string) => {
  LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
  updateSettings('theme', id);
};

LayoutAnimation 会让布局变化有个平滑的过渡效果。不过它对颜色变化的支持有限,效果可能不太明显。

更好的方案是用 react-native-reanimated 做颜色插值动画,但实现起来比较复杂。

省电效果

深色模式在 OLED 屏幕上确实能省电,因为 OLED 显示黑色时像素是关闭的,不耗电。

但在 LCD 屏幕上,深色模式不省电,甚至可能更耗电(因为背光始终亮着,显示深色需要液晶遮挡更多光线)。

所以底部提示里说"节省电量",严格来说只对 OLED 屏幕成立。不过现在大部分中高端手机都是 OLED 屏幕了,这么说问题不大。

小结

主题设置页面的 UI 和语言设置很像,就是一个选择列表。但深色模式的实现比多语言复杂。

几个关键点:

  • 提供三个选项:跟随系统、浅色、深色
  • 每个选项有图标和描述,帮助用户理解
  • 底部提示深色模式的好处

关于深色模式实现:

  • useColorScheme 获取系统主题
  • 不要用纯黑色,用深灰色
  • 文字颜色要降低对比度
  • 图片和图标可能需要适配
  • 切换时可以加过渡动画

深色模式是个细活,做好了用户体验会很好,做不好会很丑。如果时间有限,可以先只支持浅色模式,后面再加深色模式。

下一篇写隐私政策页面,这是本系列的最后一篇,敬请期待。


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

相关推荐
IvorySQL2 小时前
PostgreSQL 分区表的 ALTER TABLE 语句执行机制解析
数据库·postgresql·开源
·云扬·3 小时前
MySQL 8.0 Redo Log 归档与禁用实战指南
android·数据库·mysql
IT邦德3 小时前
Oracle 26ai DataGuard 搭建(RAC到单机)
数据库·oracle
早點睡3903 小时前
高级进阶 React Native 鸿蒙跨平台开发:@react-native-community-slider 滑块组件
react native·react.js·harmonyos
陈振wx:zchen20083 小时前
JavaScript
javascript·js
我是伪码农3 小时前
Vue 智慧商城项目
前端·javascript·vue.js
不认输的西瓜3 小时前
fetch-event-source源码解读
前端·javascript
惊讶的猫3 小时前
redis分片集群
数据库·redis·缓存·分片集群·海量数据存储·高并发写
不爱缺氧i3 小时前
完全卸载MariaDB
数据库·mariadb
纤纡.3 小时前
Linux中SQL 从基础到进阶:五大分类详解与表结构操作(ALTER/DROP)全攻略
linux·数据库·sql