React Native框架底層的手勢響應(yīng)系統(tǒng)提供了響應(yīng)處理器朗若,PanResponder API將這些手勢響應(yīng)處理器再次進行封裝,便于開發(fā)者對手勢進行處理昌罩。
PanResponser API的基本思想就是:監(jiān)視屏幕上指定的位置的矩形區(qū)域哭懈。對手指觸發(fā)的事件作出響應(yīng)。
一茎用、利用PanResponser API監(jiān)視的步驟
1遣总、指定監(jiān)視區(qū)域
為了監(jiān)視一個區(qū)域,我們需要準(zhǔn)備一個view或者是從view組件擴展而來的組件轨功。(注意:如果要監(jiān)視兩個區(qū)域旭斥,一定不能讓他們重疊,不然監(jiān)視器無法工作)
2夯辖、定義監(jiān)視器的相關(guān)變量
指向監(jiān)視器的變量(必須)琉预。
用來指向監(jiān)視器監(jiān)視區(qū)域的變量,可以不定義蒿褂。
用來記錄監(jiān)視區(qū)域左上角頂點坐標(biāo)的兩個數(shù)值變量圆米。可以不定義啄栓。但當(dāng)觸摸發(fā)生需要給用戶視覺上的反饋時娄帖,有這個變量可以很容易實現(xiàn)反饋。
上一次觸摸點的橫昙楚、縱坐標(biāo)變量近速。可以不定義,但這兩個變量可以便于分析削葱、處理觸摸事件奖亚。
3、準(zhǔn)備監(jiān)視器的事件處理函數(shù)
一共有13個這樣的函數(shù)析砸,比如說onMoveShouldSetPanResponder用來判斷是否要監(jiān)視這個區(qū)域昔字,onMoveShouldSetPanResponderCapture等。那我們只需要挑選出自己需要的函數(shù)來開發(fā)就可以了首繁。
以下是監(jiān)視器的13個事件處理函數(shù)
onMoveShouldSetPanResponder
onMoveShouldSetPanResponderCapture
onStartShouldSetPanResponder
onStartShouldSetPanResponderCapture
onPanResponderReject
onPanResponderGrant
onPanResponderStart
onPanResponderEnd
onPanResponderRelease
onPanResponderMove
onPanResponderTerminate
onPanResponderTerminateRequest
onShouldBlockNativeResponder
4作郭、建立監(jiān)視器
用PanResponder API提供的靜態(tài)函數(shù)create,建立監(jiān)聽器
this.watcher = PanResponder.create({
……
})
5弦疮、將監(jiān)視器和監(jiān)視區(qū)域掛接
我們先假設(shè)一下夹攒,監(jiān)視器就叫watcher。
{...this.watcher.panHandlers}
二胁塞、監(jiān)視事件的生命周期
一般來說咏尝,在點擊的生命周期我們自定義的被回調(diào)的函數(shù)都會收到兩個參數(shù),一個是原生事件闲先,另一個是手勢狀態(tài)状土。
而這里面會有很多的成員變量比如說觸摸點的位置无蜂,比如說手勢狀態(tài)的ID.
手勢狀態(tài)有以下變量
stateID—觸摸狀態(tài)的ID伺糠,在屏幕上至少有一個點的情況下,這個id會一直存在斥季。
moveX—最近一次移動時的屏幕橫坐標(biāo)
moveY—最近一次移動時的屏幕縱坐標(biāo)
x0—當(dāng)響應(yīng)器產(chǎn)生時的屏幕坐標(biāo)
y0—當(dāng)響應(yīng)器產(chǎn)生時的屏幕坐標(biāo)
dx—從觸摸開始累積的橫向路程
dy—從觸摸操作開始累積縱向路程
vx—當(dāng)前的橫向移動速度
vy—當(dāng)前的縱向移動速度
numberActiveTouches—當(dāng)前在屏幕上的有效觸摸點的數(shù)量训桶。
三、單次點擊事件的生命周期
onStartShouldSetPanResponderCapture:是否設(shè)置開始捕捉這次事件
onStartResponderStart:將這個事件視為點擊事件的開始點
onPanResponderEnd:將這個事件視為點擊事件的結(jié)束點酣倾。
這里列舉出的三個生命周期方法是最常見的舵揭,但是其實它還有其他很多的方法。不過我們平常用的單次點擊事件就是這三個躁锡。
在移動手勢中午绳,也有它自己的生命周期方法。這里不做詳解映之。通過下面一個小的案例進行解說盅弛。
四侨拦、案例
滑動解鎖:手指按壓的滑塊跟隨手指移動,按壓的監(jiān)視區(qū)域隨著手指移動而變化
從圖中我們可以看到,在這個RN界面中需要返回一個頂級元素view,然后在里面添加一個滑塊槽禀苦,之后是按鈕。
這個按鈕會有一個樣式呻澜,我們可以將它切成一個圓的樣子滥崩。并且,這個按鈕是需要滑動的,所以要給它添加一個表示距離滑動槽原點的位置搞糕。而這個樣式是需要及時改變的勇吊,所以我們可以定義一個狀態(tài)機。用leftPoint來表示它的位置窍仰。
export default class GusDemo extends Component {
constructor(props){
super(props);
this.watcher = null; //監(jiān)視器
this.startX = 0; //開始的左邊
this.state = {leftPoint:1} //狀態(tài)機變量用來保存最左邊的卡槽
}
返回的UI界面
render() {
return (
<View style={styles.container}>
<View style = {styles.barViewStyle}>
<View style = {[styles.buttonViewStyle,{left:this.state.leftPoint}]}
{...this.watcher.panHandlers} //將監(jiān)視器與監(jiān)視區(qū)域掛接
/>
</View>
</View>
);
}
設(shè)置樣式
首先要獲取寬度萧福。
var Dimensions = require('Dimensions');
var totalWidth = Dimensions.get('window').width; //寬度
設(shè)置樣式
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部分已經(jīng)構(gòu)建完畢辈赋,現(xiàn)在要做的就是到componentWillMount()方法里面去建立監(jiān)視器鲫忍。為啥要在這個方法里面呢,是因為這個方法在UI渲染之前運行的钥屈,我們可以讓它來做一些定義變量或賦值的操作悟民。所以我們將事件的按下、移動和結(jié)束的方法都寫到這邊來篷就。分別給這幾個屬性各自定義一個方法射亏。
componentWillMount(){
this.watcher = PanResponder.create({ //建立監(jiān)視器
onStartShouldSetPanResponder:()=>true, //判斷是否要監(jiān)聽,這里直接返回true
onPanResponderGrant:this._onPanResponderGrant, //事件,按下
onPanResponderMove:this._onPanResponderMove, //移動
onPanResponderEnd:this._onPanResponderEnd, //結(jié)束
});
}
這些方法我們都用簡寫的方式竭业,所以到構(gòu)造函數(shù)中將它們綁定一下智润。
export default class GusDemo extends Component {
constructor(props){
super(props);
this.watcher = null; //監(jiān)視器
this.startX = 0; //開始的左邊
this.state = {leftPoint:1} //狀態(tài)機變量用來保存最左邊的卡槽
this._onPanResponderGrant = this._onPanResponderGrant.bind(this);
this._onPanResponderEnd = this._onPanResponderEnd.bind(this);
this._onPanResponderMove = this._onPanResponderMove.bind(this);
}
現(xiàn)在來具體實現(xiàn)自定義的方法。雖然我們看到的是簡寫的方法未辆,但是實際上窟绷,系統(tǒng)按下的方法會給我們自定義的這個方法傳入兩個參數(shù),一個是事件咐柜,而另外一個是手指觸摸的位置兼蜈。在開始的時候,我們要將開始偏移的位置給記錄下來拙友。因為每次開始滑動的時候位置其實都是不一樣的为狸。
_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; //在后面可以寫解鎖或者是跳轉(zhuǎn)效果。
}
this.setState(()=>{
return {leftPoint}; //改變狀態(tài)機
})
}
當(dāng)手指松開之后遗契,我們在這里不做復(fù)雜的判斷辐棒,直接讓它移動到最原始的位置。
_onPanResponderEnd(e,gestureState){
let leftPoint = 1;
this.setState(()=>{
return {leftPoint}; //改變狀態(tài)機到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; //監(jiān)視器
this.startX = 0; //開始的左邊
this.state = {leftPoint:1} //狀態(tài)機變量用來保存最左邊的卡槽
this._onPanResponderGrant = this._onPanResponderGrant.bind(this);
this._onPanResponderEnd = this._onPanResponderEnd.bind(this);
this._onPanResponderMove = this._onPanResponderMove.bind(this);
}
componentWillMount(){
this.watcher = PanResponder.create({ //建立監(jiān)視器
onStartShouldSetPanResponder:()=>true,
onPanResponderGrant:this._onPanResponderGrant, //事件,按下
onPanResponderMove:this._onPanResponderMove, //移動
onPanResponderEnd:this._onPanResponderEnd, //結(jié)束
});
}
//移動的邏輯
_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; //在后面可以寫解鎖或者是跳轉(zhuǎn)效果漾根。
}
this.setState(()=>{
return {leftPoint}; //改變狀態(tài)機
})
}
_onPanResponderEnd(e,gestureState){
let leftPoint = 1;
this.setState(()=>{
return {leftPoint}; //改變狀態(tài)機到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);