RN手勢

React Native框架底層的手勢響應系統提供了響應處理器,PanResponder API將這些手勢響應處理器再次進行封裝,便于開發者對手勢進行處理。
PanResponser API的基本思想就是:監視屏幕上指定的位置的矩形區域。對手指觸發的事件作出響應。
一、利用PanResponser API監視的步驟
1、指定監視區域
為了監視一個區域,我們需要準備一個view或者是從view組件擴展而來的組件。(注意:如果要監視兩個區域,一定不能讓他們重疊,不然監視器無法工作)
2、定義監視器的相關變量
指向監視器的變量(必須)。
用來指向監視器監視區域的變量,可以不定義。
用來記錄監視區域左上角頂點坐標的兩個數值變量??梢圆欢x。但當觸摸發生需要給用戶視覺上的反饋時,有這個變量可以很容易實現反饋。
上一次觸摸點的橫、縱坐標變量??梢圆欢x,但這兩個變量可以便于分析、處理觸摸事件。
3、準備監視器的事件處理函數
一共有13個這樣的函數,比如說onMoveShouldSetPanResponder用來判斷是否要監視這個區域,onMoveShouldSetPanResponderCapture等。那我們只需要挑選出自己需要的函數來開發就可以了。
以下是監視器的13個事件處理函數

 onMoveShouldSetPanResponder
 onMoveShouldSetPanResponderCapture
 onStartShouldSetPanResponder
 onStartShouldSetPanResponderCapture
 onPanResponderReject
 onPanResponderGrant
 onPanResponderStart
 onPanResponderEnd
 onPanResponderRelease
 onPanResponderMove
 onPanResponderTerminate
 onPanResponderTerminateRequest
 onShouldBlockNativeResponder

4、建立監視器
用PanResponder API提供的靜態函數create,建立監聽器

this.watcher = PanResponder.create({
  ……
})

5、將監視器和監視區域掛接
我們先假設一下,監視器就叫watcher。

{...this.watcher.panHandlers}

二、監視事件的生命周期
一般來說,在點擊的生命周期我們自定義的被回調的函數都會收到兩個參數,一個是原生事件,另一個是手勢狀態。
而這里面會有很多的成員變量比如說觸摸點的位置,比如說手勢狀態的ID.
手勢狀態有以下變量

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

三、單次點擊事件的生命周期
onStartShouldSetPanResponderCapture:是否設置開始捕捉這次事件
onStartResponderStart:將這個事件視為點擊事件的開始點
onPanResponderEnd:將這個事件視為點擊事件的結束點。
這里列舉出的三個生命周期方法是最常見的,但是其實它還有其他很多的方法。不過我們平常用的單次點擊事件就是這三個。
在移動手勢中,也有它自己的生命周期方法。這里不做詳解。通過下面一個小的案例進行解說。

四、案例
滑動解鎖:手指按壓的滑塊跟隨手指移動,按壓的監視區域隨著手指移動而變化

75353037-EE9F-4BA8-8283-8B2F9528F7BF.png

從圖中我們可以看到,在這個RN界面中需要返回一個頂級元素view,然后在里面添加一個滑塊槽,之后是按鈕。
這個按鈕會有一個樣式,我們可以將它切成一個圓的樣子。并且,這個按鈕是需要滑動的,所以要給它添加一個表示距離滑動槽原點的位置。而這個樣式是需要及時改變的,所以我們可以定義一個狀態機。用leftPoint來表示它的位置。

export default class GusDemo extends Component {
  constructor(props){
    super(props);
    this.watcher = null;   //監視器
    this.startX = 0; //開始的左邊
    this.state = {leftPoint:1}  //狀態機變量用來保存最左邊的卡槽
}

返回的UI界面

render() {
    return (
      <View style={styles.container}>
         <View style = {styles.barViewStyle}>
            <View style = {[styles.buttonViewStyle,{left:this.state.leftPoint}]}
                  {...this.watcher.panHandlers}  //將監視器與監視區域掛接
            />
        </View>
      </View>
    );
  }

設置樣式
首先要獲取寬度。

var Dimensions = require('Dimensions');
var totalWidth = Dimensions.get('window').width;  //寬度

設置樣式

const styles = StyleSheet.create({
  container: {
    flex: 1,

    backgroundColor: '#F5FCFF',
  },
  barViewStyle: {
      width:totalWidth - 40,
      height:50,
    backgroundColor:'grey',
    borderRadius:25,
    left:20,
    top:50,
    flexDirection:'row',
  },
  buttonViewStyle: {
      width:48,
      height:48,
     borderRadius:24,
    backgroundColor:'pink',
    left:1,
    top:1,
  }
});

自此,所有的UI部分已經構建完畢,現在要做的就是到componentWillMount()方法里面去建立監視器。為啥要在這個方法里面呢,是因為這個方法在UI渲染之前運行的,我們可以讓它來做一些定義變量或賦值的操作。所以我們將事件的按下、移動和結束的方法都寫到這邊來。分別給這幾個屬性各自定義一個方法。

componentWillMount(){
    this.watcher = PanResponder.create({  //建立監視器
      onStartShouldSetPanResponder:()=>true,  //判斷是否要監聽,這里直接返回true
      onPanResponderGrant:this._onPanResponderGrant,  //事件,按下
      onPanResponderMove:this._onPanResponderMove,  //移動
      onPanResponderEnd:this._onPanResponderEnd,   //結束

    });
  }

這些方法我們都用簡寫的方式,所以到構造函數中將它們綁定一下。

export default class GusDemo extends Component {
  constructor(props){
    super(props);
    this.watcher = null;   //監視器
    this.startX = 0; //開始的左邊
    this.state = {leftPoint:1}  //狀態機變量用來保存最左邊的卡槽
    this._onPanResponderGrant = this._onPanResponderGrant.bind(this);
    this._onPanResponderEnd = this._onPanResponderEnd.bind(this);
    this._onPanResponderMove = this._onPanResponderMove.bind(this);
  }

現在來具體實現自定義的方法。雖然我們看到的是簡寫的方法,但是實際上,系統按下的方法會給我們自定義的這個方法傳入兩個參數,一個是事件,而另外一個是手指觸摸的位置。在開始的時候,我們要將開始偏移的位置給記錄下來。因為每次開始滑動的時候位置其實都是不一樣的。

 _onPanResponderGrant(e,gestureState){
    this.startX = gestureState.x0;   //按住滑塊的時候,記錄偏移量
  }

下面來寫移動按鈕的時候的邏輯

_onPanResponderMove(e,gestureState){
    let leftPoint;   //用一個變量記錄滑動的偏移值
    if(gestureState.moveX > totalWidth-42-48+this.startX){  //正常位置
      leftPoint = totalWidth - 42 - 48;
    }else{
      leftPoint = gestureState.moveX - this.startX; //在后面可以寫解鎖或者是跳轉效果。
    }
    this.setState(()=>{
      return {leftPoint};    //改變狀態機
    })
  }

當手指松開之后,我們在這里不做復雜的判斷,直接讓它移動到最原始的位置。

 _onPanResponderEnd(e,gestureState){
      let leftPoint = 1;
    this.setState(()=>{
      return {leftPoint};   //改變狀態機到1的位置
    })
  }

滑動解鎖的案例就完成了。

下面是源碼index.ios.js

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  PanResponder
} from 'react-native';
var Dimensions = require('Dimensions');
var totalWidth = Dimensions.get('window').width;  //寬度
export default class GusDemo extends Component {
  constructor(props){
    super(props);
    this.watcher = null;   //監視器
    this.startX = 0; //開始的左邊
    this.state = {leftPoint:1}  //狀態機變量用來保存最左邊的卡槽

    this._onPanResponderGrant = this._onPanResponderGrant.bind(this);
    this._onPanResponderEnd = this._onPanResponderEnd.bind(this);
    this._onPanResponderMove = this._onPanResponderMove.bind(this);


  }

  componentWillMount(){
    this.watcher = PanResponder.create({  //建立監視器
      onStartShouldSetPanResponder:()=>true,
      onPanResponderGrant:this._onPanResponderGrant,  //事件,按下
      onPanResponderMove:this._onPanResponderMove,  //移動
      onPanResponderEnd:this._onPanResponderEnd,   //結束

    });
  }

  //移動的邏輯
  _onPanResponderGrant(e,gestureState){
    this.startX = gestureState.x0;   //按住滑塊的時候,記錄偏移量
  }
  _onPanResponderMove(e,gestureState){
    let leftPoint;   //用一個變量記錄滑動的偏移值
    if(gestureState.moveX > totalWidth-42-48+this.startX){  //正常位置
      leftPoint = totalWidth - 42 - 48;
    }else{
      leftPoint = gestureState.moveX - this.startX; //在后面可以寫解鎖或者是跳轉效果。
    }
    this.setState(()=>{
      return {leftPoint};    //改變狀態機
    })
  }
  _onPanResponderEnd(e,gestureState){
      let leftPoint = 1;
    this.setState(()=>{
      return {leftPoint};   //改變狀態機到1的位置
    })
  }
  render() {
    return (
      <View style={styles.container}>
         <View style = {styles.barViewStyle}>
            <View style = {[styles.buttonViewStyle,{left:this.state.leftPoint}]}
                  {...this.watcher.panHandlers}
            />
        </View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,

    backgroundColor: '#F5FCFF',
  },
  barViewStyle: {
      width:totalWidth - 40,
      height:50,
    backgroundColor:'grey',
    borderRadius:25,
    left:20,
    top:50,
    flexDirection:'row',
  },
  buttonViewStyle: {
      width:48,
      height:48,
     borderRadius:24,
    backgroundColor:'pink',
    left:1,
    top:1,
  }
});

AppRegistry.registerComponent('GusDemo', () => GusDemo);

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,596評論 25 708
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,259評論 4 61
  • 頸椎病誤區:"米字操" 在電腦前坐久了,不少人有喜歡“搖頭晃腦”的習慣,有的稱之為“米字操”,目的是為了防頸椎病。...
    文五閱讀 487評論 0 1
  • 我深深懂得:給別人留下尊重和尊嚴,給別人留下空間和余地,才會給自己留下最美好的東西,還有值得懷念的友誼、親情和愛情!
    簡俊智閱讀 147評論 0 0
  • 打開籠子 一只白鴿飛遠了 從此 我 一個人 成了深深的孤獨患者
    伍月的晴空閱讀 201評論 7 4