我學習 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 進行完成。