请添加图片描述

案例项目开源地址: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:bgGradient1bgGradient2。它们是半透明的彩色圆形,叠加在背景上产生渐变效果。

最上层是 Animated.View,包含所有的页面内容。内容在装饰层之上,不会被遮挡。

为什么用 View 而不是真正的渐变

React Native 原生不支持 CSS 的 linear-gradientradial-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 脱离正常的文档流,可以用 toprightbottomleft 精确定位。

绝对定位的元素相对于最近的有定位属性的父元素定位。这里相对于 SafeAreaView(它默认有 position: relative)。

位置设置

top: -100right: -100 让圆形的一部分超出屏幕边界。为什么要超出?因为我们只想显示圆形的一部分,产生"光晕从屏幕外照进来"的感觉。

如果圆形完全在屏幕内,看起来就像一个圆形色块,不够自然。让它部分超出,只显示边缘的弧形,更像是光线的渐变。

尺寸和圆角

width: 300height: 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: -150left: -150 把圆形放在左下角,同样部分超出屏幕。

更大的尺寸

width: 400height: 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,这里是 108
  • g(绿色):0-255,这里是 92
  • b(蓝色):0-255,这里是 231
  • a(透明度):0-1,这里是 0.1

和十六进制的转换

我们的主题强调色是 #6c5ce7,转换成 RGB:

  • 6c = 6×16 + 12 = 108
  • 5c = 5×16 + 12 = 92
  • e7 = 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 最后,在最上面。

绝对定位不影响层叠顺序

bgGradient1bgGradient2 都是绝对定位,但这不影响它们的层叠顺序。绝对定位只是让它们脱离文档流,可以自由定位,层叠顺序还是按代码顺序。

content 为什么能覆盖装饰圆形

虽然 content 没有设置 position: 'absolute',但它在代码中出现得最晚,所以在最上面。

而且 contentflex: 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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐