1 基本使用
ScrollView 是 React Native(后面简称:RN) 中最常见的组件之一。理解 ScrollView 的原理,有利于写出高性能的 RN 应用。
ScrollView 的基本使用也非常简单,如下:
1 2 3 4 5 |
<ScrollView> <Child1 /> <Child2 /> ... </ScrollView> |
它和 View 组件一样,可以包含一个或者多个子组件。对子组件的布局可以是垂直或者水平的,通过属性 horizontal=true/false
来控制。甚至还默认支持“下拉”刷新操作。另外还有一个特别赞的特性,超出屏幕的 View 会自动被移除,从而节省资源和提高绘制效率。我们来看如下一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class ScrollViewTest extends Component { render() { let children = []; for (var i = 0; i < 20; i++) { children.push( <View key={"key_" + i} style={styles.child}> <Text>{"T" + i}</Text> </View>); } return ( <ScrollView style={styles.scrollView}> {children} </ScrollView> ); } } |
在 Android 上的效果如下:
如图,我们在 ScrollView 中添加了 20 个子组件,但是我们的屏幕任意时刻最多只能显示 5 个子项目。
下面我们来看实际对应的 Native 控件的情况。RN 中的 ScrollView 对应到 Native 的 RCTScrollView,自动把子组件包含在一个 ViewGroup 中(因为Android 的 ScrollView 只能有一个直接子控件),如下图中的红色框内:
注意到,我们在 JS 中添加了 20 个子组件,但是在 RCTViewGroup 中只有在屏幕上显示的 5 个子控件,在屏幕外的组件,也会自动添加到 View 树中,这与 Native 的 ScrollView 表现一致。
其实,RN 中的 ScrollView 有一个 removeClippedSubviews
属性,表示如果子 View 超出可视区域,是否自动移除,虽然默认是 true。但是也需要子 View 的 overflow: 'hidden'
属性配合。所以,给子组件的 style
添加如下属性即可。
1 2 3 4 5 6 7 8 9 10 |
<View key={"key_" + i} style={styles.child}> <Text>{"T" + i}</Text> </View>; const styles = StyleSheet.create({ child: { ... overflow: 'hidden', }, }); |
得到的效果是,在使用上完全没有区别,而我们来看一下界面的 Tree View,如下图:
可见,屏幕外的子 View,就被自动从 View 树中移除了。
同时,我们来看一下 iOS 平台上的表现,与 Android 上类似:
这印证了我们前面的结论,RN 自动优化了 Native 平台 ScrollView,在这个层面,我们可以说 RN 比 Native 的性能还要高。
2 性能研究
通过上面的实例,我们可以看到,ScrollView 应该是非常高效的,它使用简单,并且还能按需构建 View 树,高效渲染,有点类似 Native 平台上的 ListView 了,是我心目完美 ScrollView 该有的样子。
但是,之前看到腾讯的 TAT.ronnie 一篇文章 探索 react native 首屏渲染最佳实践,文中提到的优化方法,主要就是针对 ScrollView 的。作者认为,在 ScrollView 中,即使不可见(例如,超出屏幕)的组件还是会绘制的。为了优化 ScrollView 的绘制性能,不可见的组件,应该在 JS 中避免添加到 ScrollView 中。
显然,这与我们前面观察到的结论是矛盾的。但是,作者的通过那样处理,确实优化了显示性能,这是怎么回事呢?为了验证,我们也和文中一样,使用 componentDidMount()
和 componentWillMount()
的时间差衡量显示速度。在 Android 上,测试 ScrollView 的子组件数量分别为 10,100,1000 的时候,显示的时间,以及 APP 所占用的内存:
子组件数量 | 加载时间(ms) | 占用内存(MB) | 绘制时间*(ms) |
---|---|---|---|
10 | 309 | 19.7 | 14.666 |
100 | 1170 | 21.9 | 15.016 |
1000 | 9461 | 26.5 | 15.025 |
* 注,这里的绘制时间,是在 Tree View 中获得的 Draw 时间。
从加载时间看,时间随着子组件的数量线性增加,占用内存也有类似趋势,说明 TAT.ronnie 的改进方法确实是有效的。另外我们也注意到,随着子组件的数量增加,Draw 的时间并没有明显的变化,其实 Measure 和 Layout 时间也没有明显的变化。
说明 ScrollView 虽然有 removeClippedSubviews
属性,也确实在 View Hierarchy 中去掉了不可见的 View。但是组件的加载时间消耗资源还是随着子组件的数量成正比。
3 原因分析
来看一下 RN 中 ScrollView 的相关的源码,主要分析 Android 平台的代码,iOS 类似,就不赘述了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
// ScrollView.js var AndroidScrollView = requireNativeComponent('RCTScrollView', ScrollView, nativeOnlyProps); var AndroidHorizontalScrollView = requireNativeComponent( 'AndroidHorizontalScrollView', ScrollView, nativeOnlyProps ); var ScrollView = React.createClassiped-line" id="crayon-5812b8e4a90e6788724815-8"> var ScrollView = React.createClass如下:
它和 View 组件一样,可以包含一个或者多个子组件。对子组件的布局可以是垂直或者水平的,通过属性
在 Android 上的效果如下: 如图,我们在 ScrollView 中添加了 20 个子组件,但是我们的屏幕任意时刻最多只能显示 5 个子项目。 下面我们来看实际对应的 Native 控件的情况。RN 中的 ScrollView 对应到 Native 的 RCTScrollView,自动把子组件包含在一个 ViewGroup 中(因为Android 的 ScrollView 只能有一个直接子控件),如下图中的红色框内:
注意到,我们在 JS 中添加了 20 个子组件,但是在 RCTViewGroup 中只有在屏幕上显示的 5 个子控件,在屏幕外的组件,也会自动添加到 View 树中,这与 Native 的 ScrollView 表现一致。 其实,RN 中的 ScrollView 有一个
得到的效果是,在使用上完全没有区别,而我们来看一下界面的 Tree View,如下图: 可见,屏幕外的子 View,就被自动从 View 树中移除了。 同时,我们来看一下 iOS 平台上的表现,与 Android 上类似: 这印证了我们前面的结论,RN 自动优化了 Native 平台 ScrollView,在这个层面,我们可以说 RN 比 Native 的性能还要高。 2 性能研究通过上面的实例,我们可以看到,ScrollView 应该是非常高效的,它使用简单,并且还能按需构建 View 树,高效渲染,有点类似 Native 平台上的 ListView 了,是我心目完美 ScrollView 该有的样子。 但是,之前看到腾讯的 TAT.ronnie 一篇文章 探索 react native 首屏渲染最佳实践,文中提到的优化方法,主要就是针对 ScrollView 的。作者认为,在 ScrollView 中,即使不可见(例如,超出屏幕)的组件还是会绘制的。为了优化 ScrollView 的绘制性能,不可见的组件,应该在 JS 中避免添加到 ScrollView 中。 显然,这与我们前面观察到的结论是矛盾的。但是,作者的通过那样处理,确实优化了显示性能,这是怎么回事呢?为了验证,我们也和文中一样,使用
从加载时间看,时间随着子组件的数量线性增加,占用内存也有类似趋势,说明 TAT.ronnie 的改进方法确实是有效的。另外我们也注意到,随着子组件的数量增加,Draw 的时间并没有明显的变化,其实 Measure 和 Layout 时间也没有明显的变化。 说明 ScrollView 虽然有 3 原因分析来看一下 RN 中 ScrollView 的相关的源码,主要分析 Android 平台的代码,iOS 类似,就不赘述了。
把字符串打印出来如下 |