React Native 动态切换主题
创建主题配置和上下文
复制代码
// ThemeContext.js
import React, { Component, createContext } from 'react';
import { Appearance, AsyncStorage } from 'react-native';
// 主题配置
const themes = {
light: {
mode: 'light',
primary: '#3498db',
secondary: '#f39c12',
background: '#ffffff',
cardBackground: '#f8f9fa',
text: '#333333',
textSecondary: '#666666',
border: '#e0e0e0',
},
dark: {
mode: 'dark',
primary: '#2ecc71',
secondary: '#e74c3c',
background: '#121212',
cardBackground: '#1e1e1e',
text: '#ffffff',
textSecondary: '#bbbbbb',
border: '#333333',
},
blue: {
mode: 'blue',
primary: '#2980b9',
secondary: '#3498db',
background: '#ecf0f1',
cardBackground: '#bdc3c7',
text: '#2c3e50',
textSecondary: '#7f8c8d',
border: '#95a5a6',
}
};
export const ThemeContext = createContext();
export class ThemeProvider extends Component {
state = {
theme: themes.light // 默认主题
};
async componentDidMount() {
// 尝试加载保存的主题
try {
const savedTheme = await AsyncStorage.getItem('userTheme');
if (savedTheme && themes[savedTheme]) {
this.setState({ theme: themes[savedTheme] });
} else {
// 没有保存的主题则使用系统主题
const systemTheme = Appearance.getColorScheme() === 'dark' ? themes.dark : themes.light;
this.setState({ theme: systemTheme });
}
} catch (error) {
console.error('加载主题失败:', error);
}
// 监听系统主题变化
this.appearanceListener = Appearance.addChangeListener(({ colorScheme }) => {
if (!this.state.userSelectedTheme) { // 如果用户没有手动选择主题
this.setState({
theme: colorScheme === 'dark' ? themes.dark : themes.light
});
}
});
}
componentWillUnmount() {
if (this.appearanceListener) {
this.appearanceListener.remove();
}
}
toggleTheme = () => {
const newTheme = this.state.theme.mode === 'light' ? themes.dark : themes.light;
this.setTheme(newTheme.mode);
};
setTheme = async (themeName) => {
if (themes[themeName]) {
this.setState({
theme: themes[themeName],
userSelectedTheme: true
});
try {
await AsyncStorage.setItem('userTheme', themeName);
} catch (error) {
console.error('保存主题失败:', error);
}
}
};
render() {
return (
<ThemeContext.Provider
value={{
theme: this.state.theme,
toggleTheme: this.toggleTheme,
setTheme: this.setTheme,
themes: Object.keys(themes) // 所有可用主题列表
}}
>
{this.props.children}
</ThemeContext.Provider>
);
}
}
创建主题化高阶组件
复制代码
// withTheme.js
import React from 'react';
import { ThemeContext } from './ThemeContext';
export function withTheme(Component) {
return function ThemedComponent(props) {
return (
<ThemeContext.Consumer>
{context => <Component {...props} {...context} />}
</ThemeContext.Consumer>
);
};
}
主应用组件
复制代码
// App.js
import React from 'react';
import { ThemeProvider } from './ThemeContext';
import MainScreen from './MainScreen';
export default class App extends React.Component {
render() {
return (
<ThemeProvider>
<MainScreen />
</ThemeProvider>
);
}
}
主屏幕组件(类组件形式)
复制代码
// MainScreen.js
import React from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import { withTheme } from './withTheme';
class MainScreen extends React.Component {
render() {
const { theme, toggleTheme, setTheme, themes } = this.props;
return (
<View style={[styles.container, { backgroundColor: theme.background }]}>
<Text style={[styles.title, { color: theme.text }]}>当前主题: {theme.mode}</Text>
<TouchableOpacity
style={[styles.button, { backgroundColor: theme.primary }]}
onPress={toggleTheme}
>
<Text style={styles.buttonText}>切换主题</Text>
</TouchableOpacity>
<View style={styles.themeOptions}>
{themes.map(themeName => (
<TouchableOpacity
key={themeName}
style={[
styles.themeButton,
{
backgroundColor: themes[themeName].primary,
marginBottom: 10
}
]}
onPress={() => setTheme(themeName)}
>
<Text style={styles.buttonText}>{themeName}</Text>
</TouchableOpacity>
))}
</View>
<View style={[styles.card, { backgroundColor: theme.cardBackground }]}>
<Text style={{ color: theme.text }}>这是一个卡片示例</Text>
<Text style={{ color: theme.textSecondary }}>次要文本颜色</Text>
<View style={[styles.divider, { backgroundColor: theme.border }]} />
<Text style={{ color: theme.text }}>边框颜色示例</Text>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
title: {
fontSize: 24,
marginBottom: 30,
fontWeight: 'bold',
},
button: {
padding: 15,
borderRadius: 8,
marginBottom: 20,
width: 200,
alignItems: 'center',
},
themeButton: {
padding: 12,
borderRadius: 6,
marginHorizontal: 5,
width: 100,
alignItems: 'center',
},
buttonText: {
color: 'white',
fontWeight: 'bold',
},
themeOptions: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center',
marginTop: 20,
marginBottom: 30,
},
card: {
width: '90%',
padding: 20,
borderRadius: 10,
marginTop: 20,
},
divider: {
height: 1,
width: '100%',
marginVertical: 15,
}
});
export default withTheme(MainScreen);