寫一個輪播圖,學會 React Native

我學習 Web 的第一課,就是學習寫一個輪播圖,在寫輪播圖時天然地將 html、css、js、DOM、組件設計等各方面簡單的知識點給串起來了。學習 React Native 的時候,也天然用起了這個思路,挺好用的。本文經過寫一個輪播圖,但願幫助到那些對 React Native 有興趣的同窗。css

本文會一步一步和帶領你們實現一個輪播圖組件,幫助你們將一個個單獨的知識點給串。學習本文以前,最好對 React Native 有所瞭解。其中的一些單獨的知識點,若是不是很瞭解,能夠在學習過程當中點擊相關連接學習。這個單獨的知識點包括:html

  • Components: View、Touchble*
  • APIs: Animated、PanResponder、StyleSheet

配合 github 項目學習效果更佳:
https://github.com/jiangleo/l...java

輪播圖的最終效果圖以下:react

圖片描述

簡單輪播圖組件

接口設計

一步實現最終效果圖實現的效果是很難的,因此不如先把輪播圖設計的簡單點,而後一步一步地優化。
這個簡單的輪播圖組件,只擁有以下 3 個功能:git

  • 在展示區域默認顯示第 index 個項目的內容;
  • 右滑,在展示區域顯示上一個項目的內容;
  • 左滑,在展示區域顯示下一個項目的內容。

輪播圖的主要思想是,每次只顯示一個個項目面,超出容器個項目面被隱藏,思路圖以下: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。

Animatedstate 同樣,都符合符合聲明式編程的原理。因爲 Animated 的動畫值也能夠看作頁面的某種狀態。在官網的示例代碼中,直接將Animated 的動畫值直接掛在了 this.state 上,也證實了這一點。
下面咱們將 Animatedstate 進行對比,幫助你們進行理解:

# | 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:控制項目的位移位置

爲了組件接口的設計方便,不該該把這個底層狀態 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 有了大概的瞭解和認知。

在寫這個輪播圖的過程當中,應用了 ViewTouchble* 組件和 AnimatedPanResponderStyleSheet API。

在寫輪播圖的過程當中,還應用了小步迭代的開發方式。即實現的過程當中,將這個輪播圖分爲了三個階段進行開發:簡單輪播圖、動畫輪播圖、手勢輪播圖。每一個階段,又能夠分爲三個步驟:準備要應用的知識(google)、實現功能描述、實現。經過小步迭代的方式,能夠將一個大問題分解爲幾個小問題,再把小問題分解爲最基本的知識點,再去設法實現。

最後,這還只是一個輪播圖的雛形,還有不少優化點能夠作,好比:

  • isLoop: 是否頭尾銜接的循環輪播
  • horizontal:是否水平輪播
  • renderPager:接受一個組件,該用於處理手勢和動畫。好比可使用 ScrollView 和 ViewPagerAnder,在一些特定場景下處理手勢和動畫,達到更高的性能。
  • showsPagination:實現展示輪播提示的視圖,好比小圓點提示當前播到第幾個輪播項目了。

你們能夠參考代碼中的 SwiperAndroid 進行完成。

相關文章
相關標籤/搜索