纯色背景太单调
打开很多应用,背景就是一个纯色。深色模式是 #0f0f23,浅色模式是 #f5f5f5。功能上没问题,但总觉得少了点什么。
设计师喜欢在背景上加点"料"。渐变色、装饰图形、纹理图案,这些元素让界面更有层次感。不是花里胡哨,而是恰到好处的点缀。
我们的 TodoList 应用用了一种简单的方式:在背景上放两个半透明的大圆。一个紫色的在右上角,一个红色的在左下角。它们不会抢内容的风头,但让整个界面看起来更有设计感。
背景装饰元素的代码
先看看我们是怎么实现的:
tsx
return (
<SafeAreaView style={[styles.container, {backgroundColor: theme.bg}]}>
<StatusBar barStyle={darkMode ? 'light-content' : 'dark-content'} backgroundColor={theme.bg} />
<View style={styles.bgGradient1} />
<View style={styles.bgGradient2} />
<Animated.View style={[styles.content, {opacity: fadeAnim, transform: [{translateY: slideAnim}]}]}>
{/* 页面内容 */}
</Animated.View>
</SafeAreaView>
);
结构很简单。SafeAreaView 是最外层容器,设置了背景色。然后放了两个 View,就是那两个装饰圆。最后是 Animated.View 包裹的实际内容。
装饰元素放在内容前面,这样它们会被内容"盖住"。但因为内容区域是透明的,装饰元素还是能透出来。
装饰圆的样式
tsx
bgGradient1: {
position: 'absolute',
top: -100,
right: -100,
width: 300,
height: 300,
borderRadius: 150,
backgroundColor: 'rgba(108, 92, 231, 0.1)',
},
bgGradient2: {
position: 'absolute',
bottom: -150,
left: -150,
width: 400,
height: 400,
borderRadius: 200,
backgroundColor: 'rgba(255, 107, 107, 0.05)',
},
两个圆的样式差不多,但有一些区别。
position: 'absolute'
绝对定位,让装饰元素脱离正常的布局流。它们不会占用空间,不会影响其他元素的位置。
负数的定位值
top: -100 和 right: -100 让第一个圆的一部分超出屏幕。同样,bottom: -150 和 left: -150 让第二个圆也超出屏幕。
为什么要超出屏幕?因为这样看起来更自然。如果圆完全在屏幕内,会显得很刻意,像是"放了一个圆在那里"。超出屏幕后,用户只能看到圆的一部分,感觉像是"背景延伸到了屏幕外"。
borderRadius 等于宽高的一半
width: 300,borderRadius: 150。宽高相等,圆角等于宽高的一半,就是一个正圆。
半透明的颜色
rgba(108, 92, 231, 0.1) 是紫色,透明度 0.1,非常淡。rgba(255, 107, 107, 0.05) 是红色,透明度 0.05,更淡。
透明度很重要。太高会喧宾夺主,影响内容的可读性。太低又看不出效果。0.05 到 0.1 是一个比较好的范围。
为什么用这两个颜色
紫色 #6c5ce7 是我们应用的主题色,用在按钮、进度条、高亮等地方。红色 #ff6b6b 是高优先级任务的颜色。
背景装饰用这两个颜色,和应用的整体配色保持一致。不是随便选的颜色,而是有意义的颜色。
紫色在右上角,红色在左下角。对角线的布局让画面更有动感。如果两个圆都在同一侧,会显得不平衡。
深色模式和浅色模式
我们的装饰圆用的是固定的颜色和透明度,没有根据主题调整。这是因为:
紫色和红色在深色背景上效果不错,淡淡的光晕感。在浅色背景上也能接受,虽然不那么明显。
如果想针对不同主题调整,可以这样:
tsx
bgGradient1: {
...
backgroundColor: darkMode ? 'rgba(108, 92, 231, 0.15)' : 'rgba(108, 92, 231, 0.08)',
},
深色模式下透明度高一点,浅色模式下低一点。但这会增加代码复杂度,效果提升有限,所以我们选择了简单的固定值。
不用第三方库的渐变
React Native 原生不支持 CSS 那样的 linear-gradient。如果想要真正的渐变效果,通常需要用 react-native-linear-gradient 这样的第三方库。
但我们的方案不需要任何第三方库。用几个半透明的圆形 View,就能模拟出类似渐变的效果。这种方式:
- 不需要额外安装依赖
- 不需要原生模块链接
- 在所有平台上效果一致
- 性能开销很小
当然,这不是真正的渐变。如果需要精确的线性渐变或径向渐变,还是得用第三方库。但对于背景装饰来说,这种"伪渐变"已经够用了。
多层叠加的效果
两个圆叠加在一起,会产生一些有趣的效果。在它们重叠的区域,颜色会混合。紫色加红色,透明度叠加,形成一种微妙的过渡。
这种叠加是自然发生的,不需要额外的代码。多个半透明元素重叠,浏览器(或者说渲染引擎)会自动计算混合后的颜色。
如果想要更丰富的效果,可以加更多的装饰元素:
tsx
<View style={styles.bgGradient1} />
<View style={styles.bgGradient2} />
<View style={styles.bgGradient3} />
<View style={styles.bgGradient4} />
四个圆,四个角,或者其他的布局。但要注意不要加太多,否则会显得杂乱。两到三个通常就够了。
装饰元素的层级
在我们的代码里,装饰元素放在内容之前:
tsx
<View style={styles.bgGradient1} />
<View style={styles.bgGradient2} />
<Animated.View style={styles.content}>
{/* 内容 */}
</Animated.View>
这意味着内容会"盖在"装饰元素上面。如果内容有背景色,装饰元素就会被遮住。
我们的内容区域 styles.content 没有设置背景色,所以是透明的。装饰元素可以透过内容区域显示出来。
如果想让装饰元素在内容上面,可以调整顺序,或者用 zIndex:
tsx
bgGradient1: {
...
zIndex: 1,
},
但通常装饰元素应该在内容下面,不然会干扰用户阅读。
动态的装饰元素
静态的装饰圆已经不错了,但如果能动起来会更有趣。比如缓慢地旋转、移动、或者改变大小。
旋转动画
tsx
const rotateAnim = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.loop(
Animated.timing(rotateAnim, {
toValue: 1,
duration: 30000,
useNativeDriver: true,
})
).start();
}, []);
const spin = rotateAnim.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg'],
});
30 秒转一圈,非常慢。用户几乎感觉不到在转,但会有一种"活的"感觉。
然后把动画应用到装饰元素:
tsx
<Animated.View style={[styles.bgGradient1, {transform: [{rotate: spin}]}]} />
因为圆是对称的,旋转其实看不出来。但如果是椭圆或者其他形状,旋转就会有明显的效果。
缩放动画
tsx
const scaleAnim = useRef(new Animated.Value(1)).current;
useEffect(() => {
Animated.loop(
Animated.sequence([
Animated.timing(scaleAnim, {toValue: 1.1, duration: 5000, useNativeDriver: true}),
Animated.timing(scaleAnim, {toValue: 1, duration: 5000, useNativeDriver: true}),
])
).start();
}, []);
圆慢慢变大,再慢慢变小,循环往复。像是在"呼吸"。
tsx
<Animated.View style={[styles.bgGradient1, {transform: [{scale: scaleAnim}]}]} />
这种动画很微妙,不会分散用户注意力,但让界面更有生气。
性能考虑
装饰元素会影响性能吗?
几个静态的 View,性能影响可以忽略。它们只是普通的视图,没有复杂的计算。
如果加了动画,情况会稍微复杂一点。但只要用 useNativeDriver: true,动画在原生线程执行,不会阻塞 JS 线程。
需要注意的是,不要在装饰元素上加太多效果。比如同时有旋转、缩放、透明度变化,可能会有性能问题。保持简单,一两个动画就够了。
和主题系统的配合
我们的应用有深色和浅色两种主题:
tsx
const theme = {
bg: darkMode ? '#0f0f23' : '#f5f5f5',
card: darkMode ? '#1a1a2e' : '#ffffff',
text: darkMode ? '#ffffff' : '#333333',
subText: darkMode ? '#888888' : '#666666',
border: darkMode ? '#2a2a4a' : '#e0e0e0',
accent: '#6c5ce7',
};
背景色 theme.bg 会根据主题变化。装饰元素叠加在背景色上,效果也会不同。
深色背景 #0f0f23 上,紫色和红色的装饰圆会显得比较明显,有一种"发光"的感觉。
浅色背景 #f5f5f5 上,装饰圆会显得比较淡,更像是"阴影"或"水印"。
两种效果都可以接受。如果想要更一致的效果,可以根据主题调整装饰圆的颜色和透明度。
其他形状的装饰元素
圆形是最常用的,但不是唯一的选择。
椭圆
tsx
bgEllipse: {
position: 'absolute',
top: -50,
right: -100,
width: 400,
height: 200,
borderRadius: 100,
backgroundColor: 'rgba(108, 92, 231, 0.1)',
transform: [{rotate: '30deg'}],
},
宽高不等,就是椭圆。加上旋转,可以有更多变化。
圆角矩形
tsx
bgRect: {
position: 'absolute',
top: 100,
left: -50,
width: 200,
height: 300,
borderRadius: 20,
backgroundColor: 'rgba(255, 107, 107, 0.05)',
},
圆角矩形比圆更"硬"一些,适合更现代的设计风格。
三角形
React Native 没有直接画三角形的方式,但可以用边框技巧:
tsx
bgTriangle: {
position: 'absolute',
top: 0,
right: 0,
width: 0,
height: 0,
borderLeftWidth: 150,
borderLeftColor: 'transparent',
borderBottomWidth: 150,
borderBottomColor: 'rgba(108, 92, 231, 0.1)',
},
这会画一个直角三角形。但这种方式比较 hack,不太推荐。
模糊效果
如果想让装饰元素更柔和,可以加模糊效果。但 React Native 原生不支持 blur,需要用 @react-native-community/blur 这样的库。
不用第三方库的话,可以用多层叠加模拟模糊:
tsx
// 多个同心圆,透明度递减
<View style={[styles.bgGradient1, {width: 300, height: 300, backgroundColor: 'rgba(108, 92, 231, 0.1)'}]} />
<View style={[styles.bgGradient1, {width: 320, height: 320, backgroundColor: 'rgba(108, 92, 231, 0.05)'}]} />
<View style={[styles.bgGradient1, {width: 340, height: 340, backgroundColor: 'rgba(108, 92, 231, 0.02)'}]} />
三个圆,从内到外,透明度递减。看起来像是一个模糊的圆。但这会增加渲染的元素数量,性能上不如真正的模糊。
响应式布局
装饰元素的位置和大小是固定的。在不同尺寸的屏幕上,效果可能不一样。
小屏幕上,300x300 的圆可能占据很大比例。大屏幕上,同样的圆可能显得很小。
如果想要响应式的装饰元素,可以用 Dimensions:
tsx
import {Dimensions} from 'react-native';
const {width, height} = Dimensions.get('window');
bgGradient1: {
...
width: width * 0.8,
height: width * 0.8,
borderRadius: width * 0.4,
},
圆的大小是屏幕宽度的 80%,在不同设备上会自动调整。
但对于背景装饰来说,固定大小通常也够用。因为它们只是点缀,不需要精确适配。
小结
用两个半透明的圆形 View 作为背景装饰,不需要第三方库就能实现类似渐变的效果。position: 'absolute' 让它们脱离布局流,负数定位让它们部分超出屏幕,低透明度保证不影响内容可读性。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
