ReactNative之手勢識別

RunTitan發表於2019-04-10

ReactNative

  • 原文部落格地址: ReactNative之手勢識別
  • 移動開發中最重要的就是互動, 說到互動, 就不得不說觸控事件
  • iOS中有單擊, 雙擊, 長按, 拖拽等觸控操作
  • React Native中點選手勢都有其對應的元件, 每個元件都可以用來包裹檢視來響應使用者的點選事件

TouchableWithoutFeedback

  • 響應使用者的點選事件, 點選操作時, 元件沒有任何視覺反饋,看起來像Web效果而不是原生的效果Native
  • 是單節點元件, 只能包含一個元件, 如果你希望包含多個子元件,用一個View來包裝它們
  • 該控制元件除非你不得不使用,否則請不要使用該元件

屬性方法

  • accessibilityComponentType: View.AccessibilityComponentType
    設定可訪問的元件型別

  • accessibilityTraits: View.AccessibilityTraits,[View.AccessibilityTraits] 設定訪問特徵

  • accessible: bool
    設定當前元件是否可以訪問

  • delayLongPress: number 設定延遲的時間,單位為毫秒。從onPressIn方法開始,到onLongPress被呼叫這一段時間

  • delayPressIn: number 設定延遲的時間,單位為毫秒,從使用者觸控控制元件開始到onPressIn被呼叫這一段時間

  • delayPressOut: number
    設定延遲的時間,單位為毫秒,從使用者觸控事件釋放開始到onPressOut被呼叫這一段時間

  • onLayout: function
    當元件載入或者改元件的佈局發生變化的時候呼叫, 呼叫傳入的引數為{nativeEvent:{layout:{x,y,width,height}}}

  • onLongPress: function
    當使用者長時間按壓元件(長按效果)的時候呼叫該方法

  • onPress: function 當使用者點選的時候呼叫(觸控結束)。 但是如果事件被取消了就不會呼叫。(例如:當前被滑動事件所替代)

  • onPressIn: function
    使用者開始觸控元件回撥方法

  • onPressOut: function 使用者完成觸控元件之後回撥方法

  • pressRetentionOffset: {top:number,left:number,bottom:number,right:number} 該設定當檢視滾動禁用的情況下,可以定義當手指距離元件的距離; 當大於該距離該元件會失去響應;當少於該距離的時候,該元件會重新進行響應

該元件我們一般不會直接進行使用,下面三種Touchable*系列元件對於該元件的屬性方法都可以進行使用

TouchableOpacity

該元件封裝了響應觸控事件。當點選按下的時候,該元件的透明度會降低。該元件使用過程中並不會改變檢視的層級關係,而且我們可以非常容易的新增到應用並且不會產生額外的異常錯誤

屬性方法

  • TouchableWithoutFeedback的所有 屬性,這邊TouchableOpacity元件全部可以進行使用
  • activeOpacity: number---設定當使用者觸控的時候,元件的透明度(取值0-1)

TouchableHighlight

當手指點選按下的時候,該檢視的不透明度會進行降低同時會看到相應的顏色(檢視變暗或者變亮)。如果我們去檢視該元件的原始碼會發現,該底層實現是新增了一個新的檢視

屬性方法

  • 所有TouchableWithoutFeedback的屬性
  • activeOpacity: number---該用來設定檢視在進行觸控的時候,要要顯示的不透明度(通常在0-1之間)
  • onHideUnderlay: function---當底層被隱藏的時候呼叫
  • onShowUnderlay: function---當底層顯示的時候呼叫
  • underlayColor: 當觸控或者點選控制元件的時候顯示出的顏色

TouchableNativeFeedback

  • 僅限Android平臺
  • Android裝置上,這個元件利用原生狀態來渲染觸控的反饋
  • 目前它只支援一個單獨的View例項作為子節點

屬性方法

  • 所有TouchableWithoutFeedback的屬性
  • background: 決定在觸控反饋的時候顯示什麼型別的背景

PanResponder

相關介紹

PanResponder類可以將多點觸控操作協調成一個手勢。它使得一個單點觸控可以接受更多的觸控操作,也可以用於識別簡單的多點觸控手勢

手勢處理

React Native框架底層的手勢響應系統提供了響應處理器,PanResponder將這些手勢響應處理器再次進行封裝,以便開發者更容易對手勢進行處理,更容易預測使用者的手勢,對每一個手勢響應處理器,PanResponder除了為其提供代表觸控行為的原生事件外,還提供了一個新的手勢狀態物件用來詳細描述手勢的狀態

基本思想是:

監視螢幕上指定大小、位置的矩形區域,當用手指按壓這個區域中的某點後,開發者會收到這個事件,當按壓後拖動手指時,會收到手勢引發的各類事件,當手指離開這個矩形區域時,開發者也會收到相應的事件

注意事項:

  • 開發者可以任意指定監視矩形區域的大小,但在這個區域裡,只有第一個按下的事件會上報和繼續監視處理,如果第一個手指按下還沒有離開,接著第二個手指又來按下了,那麼對第二個手指的各種觸控事件無法捕獲
  • 開發者可以在螢幕上指定多個監視矩形區域,但是不能同時監視多個矩形區域的不同觸控事件
  • 監視區域會阻止被監視區域覆蓋的元件接收觸控事件,比如監視區域覆蓋了一個按鈕,那麼就無法通過按這個按鈕來觸發其對應的事件,只能在PanResponder監視器的事件處理中對觸控行為進行處理

使用操作

利用PanResponder實現監視器有以下幾個步驟:

指定監視區域

如果監視區域有多個,一定不能重疊,否則都失效

定義監視器相關變數

指向監視器的變數(必須有)、指向監視器監視區域的變數(可以有)、記錄監視區域左上角頂點座標的兩個數值變數(可以有)、上一次觸控點的橫縱座標變數(可以有)

事件處理

準備監視器的事件處理函式

建立監視器

PanResponder.create(config)

相關事件監聽

// 返回值為布林值, 如果返回值為 true,則表示這個 View 能夠響應滑動手勢, 兩者有一個為true即可響應
onMoveShouldSetPanResponder: (e, gestureState) => {...}
onMoveShouldSetPanResponderCapture: (e, gestureState) => {...}

// 返回值為布林值, 如果返回值為 true,則表示這個 View 能夠響應觸控手勢, 兩者有一個為true即可響應
onStartShouldSetPanResponder: (e, gestureState) => {...}
onStartShouldSetPanResponderCapture: (e, gestureState) => {...}

// 當前有其他的東西成為響應器並且沒有釋放它。如果檢視正在響應,會觸發該方法
onPanResponderReject: (e, gestureState) => {...}
// 最近一次的移動距離.如:(獲取x軸方向的移動距離 gestureState.dx)
onPanResponderGrant: (e, gestureState) => {...}
// 開始按下時的響應事件
onPanResponderStart: (e, gestureState) => {...}
// 結束按下時的響應事件
onPanResponderEnd: (e, gestureState) => {...}
// 使用者手指離開螢幕時,呼叫該方法
onPanResponderRelease: (e, gestureState) => {...}
// 使用者滑動手指時,呼叫該方法
onPanResponderMove: (e, gestureState) => {...}

// 另一個元件已經成為了新的響應者,所以當前手勢將被取消
onPanResponderTerminate: (e, gestureState) => {...}
// 如果回撥函式返回為 true,則表示同意釋放響應者角色 同時會回撥onResponderTerminate函式,通知元件事件響應處理被終止了
onPanResponderTerminationRequest: (e, gestureState) => {...}

// 返回一個布林值,決定當前元件是否應該阻止原生元件成為JS響應者(暫只支援android)
onShouldBlockNativeResponder: (e, gestureState) => {...}
複製程式碼

監視器與監視區域關聯

{…this.watcher.panHandlers}

例項:點選、拖動選擇百分百引數 比如說播放器的音量大小

引數event(e)

獲取觸控的位置在被響應的 View 中的相對座標,evt.nativeEvent.locationX

  • nativeEvent
    • changedTouches - 在上一次事件之後,所有發生變化的觸控事件的陣列集合(即上一次事件後,所有移動過的觸控點)
    • identifier - 觸控點的ID
    • locationX - 觸控點相對於父元素的橫座標
    • locationY - 觸控點相對於父元素的縱座標
    • pageX - 觸控點相對於根元素的橫座標
    • pageY - 觸控點相對於根元素的縱座標
    • target - 觸控點所在的元素ID
    • timestamp - 觸控事件的時間戳,可用於移動速度的計算
    • touches - 當前螢幕上的所有觸控點的集合

gestureState物件

  • stateID -- 觸控狀態的ID。在螢幕上有至少一個觸控點的情況下,這個ID會一直有效。
  • moveX - 最近一次移動時的螢幕橫座標
  • moveY - 最近一次移動時的螢幕縱座標
  • x0 - 當響應器產生時的螢幕座標
  • y0 - 當響應器產生時的螢幕座標
  • dx - 從觸控操作開始時的累計橫向路程
  • dy - 從觸控操作開始時的累計縱向路程
  • vx - 當前的橫向移動速度
  • vy - 當前的縱向移動速度
  • numberActiveTouches - 當前在螢幕上的有效觸控點的數量

使用示例

相關程式碼

export default class MyApp extends Component {

    constructor(props) {
        super(props)

        this.state = {
            backColor: 'red',
            left: 0,
            top: 100
        }
    }
    
    componentWillMount() {
        this._panResponse = PanResponder.create({
            onStartShouldSetPanResponder: () => true,
            onStartShouldSetPanResponderCapture: () => false,
            onMoveShouldSetPanResponder: () => true,
            onPanResponderGrant: () => {
                this._top = this.state.top
                this._left = this.state.left
                this.setState({ backColor: 'red' })
            },
            onPanResponderMove: (event, ges) => {
                console.log(`event = ${event}, guesture = ${ges}`)
                this.setState({
                    top: this._top + ges.dy,
                    left: this._left + ges.dx,
                    backColor: 'blue'
                })
            },
            onPanResponderRelease: (event, ges) => {
                this.setState({
                    backColor: 'orange'
                })
            }
        })
    }


    render() {
        return (
            <View style={styles.container}>
                <View 
                    {...this._panResponse.panHandlers}
                    style={{
                        position: 'absolute',
                        backgroundColor: this.state.backColor,
                        left: this.state.left,
                        top: this.state.top,
                        width: 50, 
                        height: 50
                    }}
                />
            </View>
        );
    }
}
複製程式碼

相關文章