React Native 中如何给 Navigator 添加一条合格的阴影

96
Pober_Wong 595a1b60 08f6 4beb 998f 2bf55e230555
0.1 2017.02.19 01:27* 字数 1652

前言

众所周知,在 React Native 中,iOS 和 Android 中缘于 Native 层实现上的差别,导致在 Js 层的 API 以及所能控制的范围完全不同,而在某些情况下 Android 上的阴影又无法显示时,我们就需要手动去做一个像模像样的阴影 (或许你永远也碰不到这种情况)。

双平台上阴影的 API 使用简介

iOS

shadowColor color
Sets the drop shadow color
shadowOffset {width: number, height: number}
Sets the drop shadow offset
shadowOpacity number
Sets the drop shadow opacity (multiplied by the color's alpha component)
shadowRadius number
Sets the drop shadow blur radius
——— react native official docs

可以看到 react native 官方文档中给的阴影相关的四个属性基本和 CSS 标准中的 box-shadow 的 是一致的。然而都是 iOS 的 ......
别急,Android 也有方案!

Android

在 Android 原生开发中,Android SDK 为了给视图增加阴影而为我们提供了 elevation 属性,顾名思义就是 “仰角”。通过为视图增加 “仰角” 方式来提供阴影,仰角越大,阴影越大。(然而并不能控制阴影的 offset......)

React Native 也沿袭了这个属性,在绝大多数情况下可以使用 elevation 为视图增加阴影。

双平台上的阴影实现效果预览

Code

export default class Demo extends Component {
  render() {
    return (
      <View style={styles.container}>
        <View style={styles.rec} />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'white',
  },
  rec: {
    height: 120,
    width: 120,
    backgroundColor: 'black',
    elevation: 20,
    shadowOffset: {width: 0, height: 0},
    shadowColor: 'black',
    shadowOpacity: 1,
    shadowRadius: 5
  }
});

Preview

iOS

Android

可以看到,Android 中仅靠仰角 elevation 是远远不够的,而 iOS 中的阴影则和 css 标准是完全看齐的。为此,在 GitHub 上也有对应以svg (react-native-svg) 为基库做成的 react-native-shadow 这个库。

双平台上对 Navigator 添加 border shadow

需求

社会需求是推动一切科学技术进步的根本源泉,这篇博文的源泉则是:

在我们的应用上使用 Navigator 在双平台上实现侧滑阴影的效果(现在 Navigation Experimental 自带阴影,然而同更多优秀的组件一样,在英文官网上并没有进行描述。ps: 本人将会在后续的博客中根据 Example 和 SourceCode 对此类组件进行一一介绍)

使用 Navigator 无侧滑阴影的效果图

without border shadow

iOS 和 Android 默认的表现是相同的,为了方便起见,就只贴一张图就行了。

思路分析

Navigator 作为路由管理者,对每个页面拥有着掌权者,其所在之处必是应用的顶层容器。而作为每个页面的管理者,想要在页面上屏幕以外的地方添加东西,必然就得在 Navigator 上做手脚。通过查询官方文档,我们找到了如下 Navigator 的如下 API:

sceneStyle
Styles to apply to the container of each scene.
// 为每一个场景所在的容器添加样式

Navigator

注:RouteStack 外的 component 会被 unmount,因此 scenes map 只是静态描述

翻了翻 Navigator 的源码,发现一个 Navigator 视图层级还挺多的,具体结构如上图。可以看得出来本质上它就是一个单独的 View,而之所以不叫 style 而是 sceneStyle 是因为每个 container of component 就是 scene 这层的容器,为了遵循编程中墨守成规的语义化命名规则也为了避免不必要的麻烦 (最外层的容器的可注入样式才叫 style)。

动手操作

原材料则是 View 的 shadow 这个样式。在开篇也介绍了,shadow 只适用于 iOS,而 Android 的仰角 elevation 则在 border shadow 上的表现几乎为零。

iOS

navigator: {
  marginLeft: -10,
  paddingLeft: 10,
  shadowColor: 'black',
  shadowOffset: {width: 0, height: 0},
  shadowOpacity: 0.4,
  shadowRadius: 5
}

只需要在 Navigator 的 sceneStyle 属性上加上这样是就可以啦~
要点解析

  • 我们使用 margin 来伸展 component 父容器到屏幕以外的位置并使用 padding 来修复同样的偏移量,这样我们的 scene 层容器变的更大了。
  • 在我们留出来的位置使用 shadow 来为整个 scene 的内容添加阴影。这里需要注意的是 shadowOffset 是用来控制阴影在横纵坐标上的偏移量的,而默认值在纵向上是负值,如果你需要在某些可伸缩的 configureScene 上使用的话,则需要在纵向上上对阴影做平衡处理,这里我们直接把两个值设为0
  • shadowRadius 阴影半径不要大于你留出来的空间,否则你懂的
  • shadowOpacity 默认值是 0,当你的阴影显示不出来的时候,看看这个值是否存在(坑爹啊)
    效果图
iOS

Android

Android 上因为使用 elevation 在侧面完全看不到阴影,我们采用了一个第三方库 —— react-native-linear-gradient,一个专门用来做颜色渐变的库,iOS 和 Android 端都使用了 Native 的实现方式。

iOS 上有两个坑

  • 颜色的渐变和 Sketch 的渐变规则不同,在视觉上大概有那么几十个像素的误差。在尝试修复多次无效后使用了 React Native 内置的 ART 通过 svg 路径自定义了视图解决了问题。
  • 使用数组作为输入值向 Native 传输值时会因为CGPoint 转换错误的原因导致 js 属性的更改并不会引起 Native 层的重新渲染。后来解决并被 react-native-community 合掉了 PR,这里是 Issues 地址
  • 本来打算在双端都使用 ART 来实现,但是在缺乏文档信息,啃了源码后发现 .......... Android 端的 Code 为空 (呵呵哒)
const easeInCubic = t => t*t*t
const input = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]
const outPut = input.map(easeInCubic).map(alpha => `rgba(0, 0, 0, ${alpha})`)
...javascript
<View style={{flex: 1}}>
  <LinearGradient
    start={{x: 0, y: 0}}
    end={{x: 1, y: 0}}
    locations={input}
    colors={outPut}
    style={{opacity: 0.3, width: 10, position: 'absolute', left: -10, top: 0, bottom: 0}}
  />
  <YourComponent />
</View>
...

要点解析
整体思路就是我们在左侧留出来的部分通过绝对布局加上了react-native-linear-gradient 这个胡汉三,直接在 Navigator 的 renderScene 这个 API 下重写即可。

  • 开始直接使用了简单默认的线性渐变,结果丑的不行,后来考虑到可能标准的阴影并不是线性渐变的
  • 很幸运,我们的 LinearGradient 组件支持 location 配合 colors 来进行多点的颜色定位,这样通过构建数组配合 map 函数生成了一组以指定渐变函数为基准的色值,也就是我们的 outPut。(这里完全使用透明度来作为渐变变量)在尝试了多个渐变函数后,发现三次曲线基本吻合 iOS 端的阴影效果,这里 是所有的 Ease Function 查询表。
    Android

    效果并没有直接模仿 iOS,而是达到了 Android 原生的基本效果。

未来的几天里,还有猛料写哦~
晚安,23 岁的自己,90 后的我们!!!

ReactX
ReactX
2.5万字 · 7.6万阅读 · 22人关注
Web note ad 1