我学习 Web 的第一课,就是学习写一个轮播图,在写轮播图时天然地将 html、css、js、DOM、组件设计等各方面简单的知识点给串起来了。学习 React Native 的时候,也天然用起了这个思路,挺好用的。本文经过写一个轮播图,但愿帮助到那些对 React Native 有兴趣的同窗。css
本文会一步一步和带领你们实现一个轮播图组件,帮助你们将一个个单独的知识点给串。学习本文以前,最好对 React Native 有所了解。其中的一些单独的知识点,若是不是很了解,能够在学习过程当中点击相关连接学习。这个单独的知识点包括:html
配合 github 项目学习效果更佳:
https://github.com/jiangleo/l...java
轮播图的最终效果图以下:react
一步实现最终效果图实现的效果是很难的,因此不如先把轮播图设计的简单点,而后一步一步地优化。
这个简单的轮播图组件,只拥有以下 3 个功能:git
轮播图的主要思想是,每次只显示一个个项目面,超出容器个项目面被隐藏,思路图以下:github
图片来源spring
为了达到复用的效果,还须要将组件调用方和组件自己分离。即组件自己只有一个,可是能够被屡次调用。编程
在明确简单轮播图组件的设计要求后,就很天然地设计出其调用方式:react-native
style
: 设置外部容器的样式。index
: 控制组件展现第 index
项目。onChange
: 当用户点击上一个按钮、点击下一个按钮触发,并经过回调参数通知调用方,index
应该怎么改变。children
: 全部轮播项目。state={ index: 0, } render() { return ( <Swiper style={{with: 100}} index={this.state.index} onChange={(index)=> { this.setState({ index: index }) }} > <View /> <View /> <View /> </Swiper> ); }
实现轮播的核心原理是,当 index
变化时,改变 Swiper 全部轮播项目的 translateX
值。超出 Swiper 容器的轮播项目会被隐藏,因此只会展示当前的第 index
个项目。其中有一个等式:函数
轮播项目位移距离 = - 当前展现的项 * 外部容器宽度 translateX = - index * layoutWidth
在渲染以前,外部容器宽度 layoutWidth
是不知道的。所以只能在外部容器渲染后,经过 onLayout
函数,来获取外部容器宽度。在获取宽度后,再将正在的轮播项目渲染出来。可是这样作,须要两次渲染才能将轮播图显示出来。在一些对性能要求高的项目中,能够经过暴露一个外部容器初始化宽度 initialWidth
的接口来提早获取,避免两次渲染。
initialWidth
: 外部容器初始化宽度另外,我写代码的时候,有个小技巧,边写边测,经过小步迭代的方式,进行快速进行开发。所以,左滑、右滑切换的功能,不妨先用上一个、下一个按钮来代替。
其核心代码,以下:
_handleLayout = ({nativeEvent}) => { this.setState({ layoutWidth: nativeEvent.layout.width, }) } render() { const {children, style, index} = this.props; const translateX = - index * this.state.layoutWidth; const items = children.map((item, index) => React.cloneElement( item, { key: index, style: [ ...item.props.style, { width: this.state.layoutWidth, transform: [{translateX,}], } ] }, )) return ( <View style={[styles.container,style]} onLayout={this._handleLayout} > {items} </View> ) }
Animated
声明式动画动画功能会用到 Animated
这个 API。
Animated
和 state
同样,都符合符合声明式编程的原理。因为 Animated
的动画值也能够看作页面的某种状态。在官网的示例代码中,直接将Animated
的动画值直接挂在了 this.state
上,也证实了这一点。
下面咱们将 Animated
和 state
进行对比,帮助你们进行理解:
# | Animated | state
声明 | this.animKey = animValue}
| this.state={stateKey: stateValue}
--| --| --
赋值 | <Animated.View props={this.animKey}>
| <View props={this.state.stateKey}>
改变状态 | this.animKey.setValue(newAnimValue) | this.setState({stateKey: newStateValue})
改变状态_动画曲线形式 | Animated.spring(this.animKey, {toValue: newAnimValue}).toStart()
| 无
在完成轮播图组件的基础切换功能的基础上,要给它添加动画功能:
一开始咱们使用 index
这个属性来控制要展示的项目。由于动画会有中间值,好比介于 0 和 1 之间的值,因此咱们须要一个新的值来表示项目的位置。
为了组件接口的设计方便,不该该把这个底层状态 positionAnimated
暴露给组件调用方去处理。组件调用方依旧只须要控制 index
便可动画改变当前展现的项目。而在组件内部,监听 index
的更新,而后驱动 positionAnimated
的改变项目位置便可。
动画版轮播图的核心原理和最初的简单版相似:
translateX = - index * layoutWidth
核心代码以下:
scrollTo = ( toIndex ) => { Animated.spring(this.state.positionAnimated, { toValue: - toIndex * this.state.layoutWidth, friction: 12, tension: 50, }).start() } render() { // ... const items = children.map((item, index) => ( <Animated.View style={{ width: layoutWidth, transform: [{ translateX: this.state.positionAnimated }], }} key={index} > {item} </Animated.View> )); // ... }
React Native 的手势事件相似于 Web,但 React Native 的手势事件更增强大和灵活。
二者类似点有:
# | React Native | Web
--|--|--
开始触碰 | onPanResponderGrant | touchstart
开始移动 | onResponderMove | touchmove
结束触碰 | onResponderRelease | touchend
意外取消 | onResponderTerminate | touchcancel
二者不一样点在于,React Native 能够针对具体元素绑定手势,而在 Web 中只能针对全局 document
进行手势监听。
在 React Native 手势接口设计上,你们能够先思考一个问题。由于 React Native 容许两个元素同时监听手势事件,若是两个元素都监听了手势,那么 React Native 应该响应那个元素呢?在 React Native 中设计了,成为响应者 Responder
的概念。大概能够描述为:若是没有响应者,任何元素均可以成为响应者;若是有元素是响应者,必须当前响应元素赞成再也不继续成为响应者后,其余元素才能变成响应者。总而言之,React Native 经过元素间的谈判,保障了手势响应者只有一个。谈判接口主要有:
# | React Native | Web
--|--|--
开始触碰,是否成为响应者 | onStartShouldSetPanResponder => boolean | 无
开始移动,是否成为响应者 | onMoveShouldSetPanResponder => boolean | 无
有其余响应者,是否释放响应权 | onPanResponderTerminationRequest => boolean | 无
以上手势事件很是底层,写起来也很复杂。而一块儿简单的手势事件,如 click 事件,并不须要这么复杂。为此 React Native 基于以上手势事件,提供了 TouchableHighlight
等组件。该组件封装了一些经常使用的点击事件和点击相关的配置,如: onPress
(click)、underlayColor
点击态背景色等。
在写简单轮播图时,用的是点击事件来代替滑动事件。点击事件的处理,用到的就是 TouchableHighlight
组件。
手势轮播图在动画轮播图上进行了升级,它须要支持如下功能:
当用户滑动时,须要相应的改变 positionAnimated
的值,使轮播图跟着手指移动。这里有个等式:
最终的位置 = 开始的位置 + 手势移动过的距离 position = startPosition + movePosition
开始的位置,须要在轮播图响应手势时 onPanResponderGrant
记录。手势移动过的距离能够在手势移动时 onResponderMove
获取,与此同时经过 positionAnimated.setValue(position)
改变轮播图的位置,让轮播图跟着手指移动。
左滑、右滑,是在用户抬起手指时 onResponderRelease
开始触发,触发的临界点咱们能够简单的设置为外部容器一半的宽度。而后经过 onChange
事件告诉,调用方要改变的位置是什么,由调用方位移轮播图。
实现的核心代码以下:
onPanResponderEnd = () => { // 超过 50% 的距离,触发左滑、右滑 const index = Math.round(-this.position / this.state.layoutWidth) const safeIndex = this.getSafeIndex(index); this.props.onChange(safeIndex) }; responder = PanResponder.create({ onPanResponderGrant: (evt, gestureState) => { // 用户手指触碰屏幕,中止动画 this.state.positionAnimated.stopAnimation(); // 记录手势响应时的位置 this.startPosition = this.position; }, onPanResponderMove: (evt, { dx }) => { // 要变化的位置 = 手势响应时的位置 + 移动的距离 const position = this.startPosition + dx this.state.positionAnimated.setValue(position) }, onPanResponderRelease: this.onPanResponderEnd, onPanResponderTerminate: this.onPanResponderEnd, });
到此一个 React Native 轮播图的也已经实现了,相信你们也应该对 React Native 有了大概的了解和认知。
在写这个轮播图的过程当中,应用了 View
、Touchble*
组件和 Animated
、PanResponder
、StyleSheet
API。
在写轮播图的过程当中,还应用了小步迭代的开发方式。即实现的过程当中,将这个轮播图分为了三个阶段进行开发:简单轮播图、动画轮播图、手势轮播图。每一个阶段,又能够分为三个步骤:准备要应用的知识(google)、实现功能描述、实现。经过小步迭代的方式,能够将一个大问题分解为几个小问题,再把小问题分解为最基本的知识点,再去设法实现。
最后,这还只是一个轮播图的雏形,还有不少优化点能够作,好比:
你们能够参考代码中的 SwiperAndroid 进行完成。