rn_for_openharmony_用两个圆形让界面不再单调:渐变背景装饰的实现
WanAndroid App通过添加半透明圆形色块增强界面层次感。实现方案是在深色背景上叠加两个绝对定位的View:右上角300x300紫色圆形(10%透明度)和左下角400x400红色圆形(5%透明度)。这种"渐变光斑"效果通过简单CSS实现,无需第三方库,既提升视觉质感又保持性能优化。圆形部分超出屏幕边缘形成自然光晕,渲染顺序确保内容层在上方显示。该设计遵循"若隐

案例项目开源地址:https://atomgit.com/nutpi/wanandroid_rn_openharmony
纯色背景看久了会觉得单调。很多现代 App 会在背景上加一些渐变色块,让界面更有层次感。
我们的 WanAndroid App 也用了这个技巧:在深色背景上放两个半透明的彩色圆形,一个在右上角,一个在左下角。
效果是什么样的
打开 App,你会看到深蓝色的背景上有两团淡淡的颜色。右上角是紫色的光晕,左下角是红色的光晕。它们不会抢眼,但让整个界面更有质感。
这种效果在设计圈叫"渐变光斑"或"模糊渐变",很多科技类 App 都在用。
实现代码
<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, ...]}>
{/* 页面内容 */}
</Animated.View>
</SafeAreaView>
结构分析
整个布局分三层:
最底层是 SafeAreaView,设置了背景色 theme.bg。这是整个页面的底色,深色模式下是深蓝黑色。
中间层是两个装饰性的 View:bgGradient1 和 bgGradient2。它们是半透明的彩色圆形,叠加在背景上产生渐变效果。
最上层是 Animated.View,包含所有的页面内容。内容在装饰层之上,不会被遮挡。
为什么用 View 而不是真正的渐变
React Native 原生不支持 CSS 的 linear-gradient 或 radial-gradient。要实现真正的渐变,需要用第三方库如 react-native-linear-gradient。
我们用了一个更简单的方案:用半透明的纯色圆形模拟渐变效果。虽然不是真正的渐变,但视觉效果很接近,而且不需要额外依赖。
第一个装饰圆形
bgGradient1: {
position: 'absolute',
top: -100,
right: -100,
width: 300,
height: 300,
borderRadius: 150,
backgroundColor: 'rgba(108, 92, 231, 0.1)',
},
绝对定位
position: 'absolute' 让这个 View 脱离正常的文档流,可以用 top、right、bottom、left 精确定位。
绝对定位的元素相对于最近的有定位属性的父元素定位。这里相对于 SafeAreaView(它默认有 position: relative)。
位置设置
top: -100 和 right: -100 让圆形的一部分超出屏幕边界。为什么要超出?因为我们只想显示圆形的一部分,产生"光晕从屏幕外照进来"的感觉。
如果圆形完全在屏幕内,看起来就像一个圆形色块,不够自然。让它部分超出,只显示边缘的弧形,更像是光线的渐变。
尺寸和圆角
width: 300 和 height: 300 设置圆形的大小。300 像素是一个比较大的尺寸,能覆盖屏幕角落的一大片区域。
borderRadius: 150 是宽高的一半,让矩形变成正圆。这是 CSS 里画圆的标准技巧:borderRadius 等于宽高的一半。
颜色
backgroundColor: 'rgba(108, 92, 231, 0.1)' 是关键。
rgba 是带透明度的颜色格式:红、绿、蓝、透明度。
108, 92, 231 是紫色,和我们的主题强调色 #6c5ce7 一致。用十六进制转换:6c = 108,5c = 92,e7 = 231。
0.1 是透明度,只有 10%。非常淡,不会喧宾夺主,只是给背景增加一点颜色变化。
第二个装饰圆形
bgGradient2: {
position: 'absolute',
bottom: -150,
left: -150,
width: 400,
height: 400,
borderRadius: 200,
backgroundColor: 'rgba(255, 107, 107, 0.05)',
},
位置在左下角
bottom: -150 和 left: -150 把圆形放在左下角,同样部分超出屏幕。
更大的尺寸
width: 400 和 height: 400,比第一个圆形大。两个圆形大小不同,避免对称,看起来更自然。
不同的颜色
rgba(255, 107, 107, 0.05) 是红色,和我们的危险色 #ff6b6b 一致。
透明度只有 0.05,比第一个圆形更淡。因为这个圆形更大,如果透明度一样会太显眼。
颜色搭配的考量
紫色和红色是我们 App 的主题色。用这两个颜色做装饰,和整体风格统一。
如果用绿色或蓝色,虽然也能产生渐变效果,但会和主题色冲突,看起来不协调。
为什么放在内容下面
<SafeAreaView>
<View style={styles.bgGradient1} /> {/* 先渲染,在下面 */}
<View style={styles.bgGradient2} /> {/* 先渲染,在下面 */}
<Animated.View> {/* 后渲染,在上面 */}
{/* 内容 */}
</Animated.View>
</SafeAreaView>
渲染顺序决定层级
在 React Native 里,后渲染的元素会覆盖先渲染的元素(除非用 zIndex 改变)。
装饰圆形先渲染,内容后渲染,所以内容在上面。用户看到的是:背景色 → 装饰圆形 → 页面内容,从下到上叠加。
为什么不用 zIndex
可以用 zIndex 明确指定层级,但在这个场景下没必要。按渲染顺序自然叠加就行,代码更简单。
zIndex 在 React Native 里有一些坑,比如在 Android 上可能不生效,能不用就不用。
透明度的选择
为什么用 0.1 和 0.05 这么低的透明度?
不能太高
如果透明度是 0.5,圆形会很明显,像是界面上有两个色块,很突兀。
装饰元素的原则是"若隐若现",让用户感觉到有什么东西,但不会注意到具体是什么。
不能太低
如果透明度是 0.01,几乎看不见,加了等于没加。
0.1 和 0.05 是经过调试的值,在深色背景上刚好能看到淡淡的颜色。
深色和浅色模式的差异
我们的装饰圆形在两种模式下都用同样的颜色和透明度。在深色背景上效果更明显,在浅色背景上几乎看不见。
如果想在浅色模式下也有明显效果,可以根据 darkMode 调整透明度:
backgroundColor: darkMode ? 'rgba(108, 92, 231, 0.1)' : 'rgba(108, 92, 231, 0.05)',
我们没有这样做,因为浅色模式本身就比较明亮,不需要额外的装饰。
性能考量
静态元素
装饰圆形是静态的,不会随用户操作变化。React Native 会把它们渲染一次,之后不会重新渲染,性能开销很小。
没有用图片
有些 App 会用图片做背景装饰,但图片需要加载,会增加包体积和内存占用。
我们用纯色 View,不需要加载任何资源,渲染速度快,内存占用小。
没有用模糊效果
真正的"光晕"效果需要模糊(blur),但 React Native 的模糊效果性能不好,尤其是在低端设备上。
我们用半透明纯色模拟,效果接近但性能好得多。
可以怎么扩展
添加更多圆形
可以加第三个、第四个圆形,放在不同位置,用不同颜色。但要注意不要太多,否则会显得杂乱。
添加动画
可以让圆形缓慢移动或呼吸(大小变化),增加动感。但要注意性能,持续的动画会消耗电量。
const breathAnim = useRef(new Animated.Value(1)).current;
useEffect(() => {
Animated.loop(
Animated.sequence([
Animated.timing(breathAnim, {toValue: 1.1, duration: 3000, useNativeDriver: true}),
Animated.timing(breathAnim, {toValue: 1, duration: 3000, useNativeDriver: true}),
])
).start();
}, []);
<Animated.View style={[styles.bgGradient1, {transform: [{scale: breathAnim}]}]} />
根据页面变化颜色
可以根据当前页面切换装饰圆形的颜色,比如首页用紫色,项目页用蓝色。增加页面的辨识度。
完整的装饰代码
// App.tsx
<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>
// 样式
const styles = StyleSheet.create({
container: {flex: 1},
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)',
},
content: {flex: 1, paddingHorizontal: 16},
});
SafeAreaView 的作用
<SafeAreaView style={[styles.container, {backgroundColor: theme.bg}]}>
什么是 SafeAreaView
SafeAreaView 是 React Native 提供的组件,用于处理设备的"安全区域"。现代手机有刘海屏、圆角屏、底部手势条等,如果内容延伸到这些区域,可能会被遮挡或者看起来很奇怪。
SafeAreaView 会自动计算安全区域,让内容只显示在不会被遮挡的区域内。在有刘海的设备上,顶部会自动留出刘海的高度;在有底部手势条的设备上,底部会自动留出手势条的高度。
为什么背景色要设置在 SafeAreaView 上
{backgroundColor: theme.bg} 设置在 SafeAreaView 上,这样整个安全区域都有背景色。如果设置在内部的 View 上,安全区域外面(比如刘海两侧)可能会显示默认的白色或黑色,不好看。
样式数组的写法
style={[styles.container, {backgroundColor: theme.bg}]} 用数组合并两个样式对象。styles.container 是静态样式(flex: 1),后面的对象是动态样式(背景色根据主题变化)。
React Native 会把数组里的样式对象合并,后面的会覆盖前面的同名属性。
StatusBar 的配置
<StatusBar barStyle={darkMode ? 'light-content' : 'dark-content'} backgroundColor={theme.bg} />
StatusBar 是什么
StatusBar 是手机顶部的状态栏,显示时间、电量、信号等信息。React Native 允许我们控制状态栏的样式。
barStyle 属性
barStyle 控制状态栏文字和图标的颜色:
'light-content':白色文字和图标,适合深色背景'dark-content':黑色文字和图标,适合浅色背景
我们根据 darkMode 动态设置:深色模式用白色文字,浅色模式用黑色文字。这样状态栏的内容始终清晰可见。
backgroundColor 属性
backgroundColor={theme.bg} 设置状态栏的背景色,和页面背景保持一致。这个属性主要在 Android 上生效,iOS 的状态栏背景是透明的。
如果不设置,Android 上状态栏可能是默认的灰色或黑色,和页面背景不协调。
装饰圆形的数学关系
bgGradient1: {
width: 300,
height: 300,
borderRadius: 150,
top: -100,
right: -100,
},
为什么 borderRadius 是宽高的一半
在 CSS 和 React Native 里,borderRadius 设置圆角的半径。当半径等于宽度(或高度)的一半时,四个角的圆弧刚好连成一个完整的圆。
数学上,一个正方形内切圆的半径等于边长的一半。所以 300x300 的正方形,borderRadius: 150 就变成直径 300 的圆。
如果 borderRadius 小于一半,比如 100,就会得到一个圆角矩形。如果大于一半,比如 200,效果和等于一半一样,因为圆角不能超过边长的一半。
为什么偏移量是 -100
圆形直径 300,偏移 -100,意味着有 200 像素在屏幕内,100 像素在屏幕外。
屏幕内的部分大约是圆形的 2/3,形成一个弧形区域。这个比例看起来比较自然,既能看到明显的颜色,又不会太突兀。
如果偏移 -150(一半),屏幕内只有半个圆,面积太小。如果偏移 -50,几乎整个圆都在屏幕内,看起来像一个色块而不是光晕。
rgba 颜色格式详解
backgroundColor: 'rgba(108, 92, 231, 0.1)',
rgba 的四个参数
r(红色):0-255,这里是 108g(绿色):0-255,这里是 92b(蓝色):0-255,这里是 231a(透明度):0-1,这里是 0.1
和十六进制的转换
我们的主题强调色是 #6c5ce7,转换成 RGB:
6c= 6×16 + 12 = 1085c= 5×16 + 12 = 92e7= 14×16 + 7 = 231
所以 #6c5ce7 等于 rgb(108, 92, 231),加上透明度就是 rgba(108, 92, 231, 0.1)。
为什么用 rgba 而不是 hex
十六进制颜色也可以带透明度,比如 #6c5ce71a(最后两位是透明度,1a 约等于 10%)。但 rgba 更直观,透明度用 0-1 的小数表示,一眼就能看出是多透明。
层叠顺序的原理
<SafeAreaView>
<View style={styles.bgGradient1} />
<View style={styles.bgGradient2} />
<Animated.View style={styles.content}>
{/* 内容 */}
</Animated.View>
</SafeAreaView>
默认的层叠规则
在 React Native 里,同级元素按照在代码中出现的顺序层叠:后面的元素在上面。
bgGradient1 最先出现,在最下面。bgGradient2 其次,在 bgGradient1 上面。content 最后,在最上面。
绝对定位不影响层叠顺序
bgGradient1 和 bgGradient2 都是绝对定位,但这不影响它们的层叠顺序。绝对定位只是让它们脱离文档流,可以自由定位,层叠顺序还是按代码顺序。
content 为什么能覆盖装饰圆形
虽然 content 没有设置 position: 'absolute',但它在代码中出现得最晚,所以在最上面。
而且 content 有 flex: 1,会占满剩余空间,覆盖在装饰圆形上面。装饰圆形透过 content 的透明部分显示出来。
不同屏幕尺寸的适配
bgGradient1: {
width: 300,
height: 300,
top: -100,
right: -100,
},
固定尺寸的问题
我们用了固定的像素值:300、400、-100、-150。在不同尺寸的屏幕上,效果会有差异。
大屏幕上,装饰圆形相对较小,效果不明显。小屏幕上,装饰圆形相对较大,可能太显眼。
如何做响应式
可以用 Dimensions API 获取屏幕尺寸,然后按比例计算:
import {Dimensions} from 'react-native';
const {width, height} = Dimensions.get('window');
bgGradient1: {
width: width * 0.8,
height: width * 0.8,
borderRadius: width * 0.4,
top: -width * 0.25,
right: -width * 0.25,
},
这样装饰圆形的大小会随屏幕尺寸变化,在各种设备上保持相似的视觉效果。
我们的项目用固定值是为了简单,在大多数手机上效果都还可以。如果你的项目需要适配平板或折叠屏,建议用响应式方案。
完整的装饰代码
// App.tsx
<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>
// 样式
const styles = StyleSheet.create({
container: {flex: 1},
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)',
},
content: {flex: 1, paddingHorizontal: 16},
});
两个简单的 View,几行样式代码,就能让界面从"能用"变成"好看"。
设计不一定要复杂,有时候一点点小心思就能带来很大的提升。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)