最近的react-native項(xiàng)目中需要在嵌套元素之間切換獲得手勢(shì)的響應(yīng)權(quán),那么作為前端的我,第一反應(yīng)就是 捕獲-冒泡 模型。故深入了解了一下react-native的panResponder-Api。并在此記錄,以供日后中年的我可以擺脫 ***脫發(fā) ***的煩惱。
首先 react-native 出于原生而勝于原生(開發(fā)效率上),所以它的手勢(shì)行為幾乎與原生一致的。但是沒(méi)有原生那樣可以深度定制,不過(guò)基本能解決大部分場(chǎng)景。下面這張圖是不是 跟 甲蟲-卡布達(dá)很像,這讓我想起了鐵架小寶,還有帥氣的水嶋宏主演的假面騎士(不小心暴露了年齡 ( ̄o ̄).zZ )
它的手勢(shì)行為 是基于“ 捕獲-冒泡 ”的詢問(wèn)模型,這一點(diǎn)和web端的事件行為倒是很像。怎么理解呢?
要想獲得該次手勢(shì)的響應(yīng)權(quán)是分2個(gè)階段的:
一、基于onStartShouldSetPanResponderCapture/onStartShouldSetPanResponder 的詢問(wèn)
時(shí)機(jī) : 發(fā)生在手指剛觸摸到屏幕的時(shí)候
-
onStartShouldSetPanResponderCapture 捕獲詢問(wèn)
會(huì)從根元素一直詢問(wèn)到手指觸碰到的元素(一般該元素不再有子元素),如果在詢問(wèn)中途有元素獲得了響應(yīng)權(quán),那么這次基于start的詢問(wèn)就結(jié)束了。
-
onStartShouldSetPanResponder 冒泡詢問(wèn)
若捕獲詢問(wèn)階段沒(méi)有元素請(qǐng)求成為響應(yīng)者,那么就從最內(nèi)層的元素開始冒泡詢問(wèn),這過(guò)程中有元素獲得響應(yīng)權(quán),那么這次基于start的詢問(wèn)就結(jié)束了
而在start階段獲得響應(yīng)者的元素在move階段是作為詢問(wèn)的最內(nèi)層元素。就像相當(dāng)于在move詢問(wèn)階段的范圍可能會(huì)縮小。
二、基于onMoveShouldSetPanResponderCapture/onMoveShouldSetPanResponder 的詢問(wèn)
這是 唯一的方式 重新分配該次手勢(shì)的響應(yīng)權(quán)
時(shí)機(jī) : 手指開始在屏幕移動(dòng)的時(shí)候
-
onMoveShouldSetPanResponderCapture 捕獲詢問(wèn)
與start詢問(wèn)階段一樣,從根元素開始,一直詢問(wèn)是否需要獲得手勢(shì)權(quán),不過(guò)這里詢問(wèn)的終點(diǎn)不再是觸摸元素,而是由start產(chǎn)生的獲權(quán)者。關(guān)鍵的事情說(shuō)三變?。?!
─=≡Σ(((つ??ω??)つ
[ move階段的 捕獲詢問(wèn)的終點(diǎn)和冒泡詢問(wèn)的起點(diǎn)不再是你以為的觸摸元素,而是由start詢問(wèn)產(chǎn)生的獲權(quán)元素 ]
[ move階段的 捕獲詢問(wèn)的終點(diǎn)和冒泡詢問(wèn)的起點(diǎn)不再是你以為的觸摸元素,而是由start詢問(wèn)產(chǎn)生的獲權(quán)元素 ]
[ move階段的 捕獲詢問(wèn)的終點(diǎn)和冒泡詢問(wèn)的起點(diǎn)不再是你以為的觸摸元素,而是由start詢問(wèn)產(chǎn)生的獲權(quán)元素 ]
─=≡Σ(((つ??ω??)つ并且你如果希望別的元素可以獲得手勢(shì)權(quán),就必須給在start階段獲取手勢(shì)權(quán)的元素設(shè)置
onPanResponderTerminatinRequest: ($e,$gs)=>true。表示當(dāng)自己獲得手勢(shì)權(quán)的時(shí)候,有別的元素申請(qǐng)獲得手勢(shì)權(quán),它將讓出這次響應(yīng)權(quán),這也是分配響應(yīng)權(quán)較關(guān)鍵的一步!!!
-
onMoveShouldSetPanResponder 冒泡詢問(wèn)
同start詢問(wèn)機(jī)制吧。
附上示例DEMO
import React from 'react';
import {View, StyleSheet, PanResponder, Text} from "react-native";
export default class Demo extends React.Component {
pan1 = PanResponder.create({
onStartShouldSetPanResponderCapture: (_,$gs) => {
console.log(JSON.stringify($gs))
console.log('%cpan1', 'color:orange', 'onStartShouldSetPanResponderCapture')
},
onStartShouldSetPanResponder: () => {
console.log('%cpan1', 'color:orange', 'onStartShouldSetPanResponder')
return true
},
onMoveShouldSetPanResponderCapture: () => {
console.log('%cpan1', 'color:orange', 'onMoveShouldSetPanResponderCapture')
},
onMoveShouldSetPanResponder: () => {
console.log('%cpan1', 'color:orange', 'onMoveShouldSetPanResponder')
},
onPanResponderTerminationRequest: () => {
console.log('%cpan1', 'color:orange', 'onPanResponderTerminationRequest')
},
onPanResponderGrant: () => {
console.log('%cpan1', 'color:orange', 'onPanResponderGrant')
},
onPanResponderMove: (_,$gs) => {
console.log(JSON.stringify($gs))
console.log('%cpan1', 'color:orange', 'onPanResponderMove')
},
onPanResponderRelease: () => {
console.log('%cpan1', 'color:orange', 'onPanResponderRelease')
},
})
pan2 = PanResponder.create({
onStartShouldSetPanResponderCapture: () => {
console.log('%cpan2', 'color:orange', 'onStartShouldSetPanResponderCapture')
},
onStartShouldSetPanResponder: () => {
console.log('%cpan2', 'color:orange', 'onStartShouldSetPanResponder')
},
onMoveShouldSetPanResponderCapture: () => {
console.log('%cpan2', 'color:orange', 'onMoveShouldSetPanResponderCapture')
// return true
},
onMoveShouldSetPanResponder: () => {
console.log('%cpan2', 'color:orange', 'onMoveShouldSetPanResponder')
},
onPanResponderTerminationRequest: () => {
console.log('%cpan2', 'color:orange', 'onPanResponderTerminationRequest')
},
onPanResponderGrant: () => {
console.log('%cpan2', 'color:orange', 'onPanResponderGrant')
},
onPanResponderMove: () => {
console.log('%cpan2', 'color:orange', 'onPanResponderMove')
},
onPanResponderRelease: () => {
console.log('%cpan2', 'color:orange', 'onPanResponderRelease')
},
})
pan3 = PanResponder.create({
onStartShouldSetPanResponderCapture: () => {
console.log('%cpan3', 'color:orange', 'onStartShouldSetPanResponderCapture')
},
onStartShouldSetPanResponder: () => {
console.log('%cpan3', 'color:orange', 'onStartShouldSetPanResponder')
},
onMoveShouldSetPanResponderCapture: () => {
console.log('%cpan3', 'color:orange', 'onMoveShouldSetPanResponderCapture')
},
onMoveShouldSetPanResponder: () => {
console.log('%cpan3', 'color:orange', 'onMoveShouldSetPanResponder')
},
onPanResponderTerminationRequest: () => {
console.log('%cpan3', 'color:orange', 'onPanResponderTerminationRequest')
},
onPanResponderGrant: () => {
console.log('%cpan3', 'color:orange', 'onPanResponderGrant')
},
onPanResponderMove: () => {
console.log('%cpan3', 'color:orange', 'onPanResponderMove')
},
onPanResponderRelease: () => {
console.log('%cpan3', 'color:orange', 'onPanResponderRelease')
},
})
render() {
return (
<View
style={styles.pan1}
{...this.pan1.panHandlers}
>
<View
style={styles.pan2}
{...this.pan2.panHandlers}
>
<View
style={styles.pan3}
{...this.pan3.panHandlers}
>
<Text>responder 詢問(wèn)模型詳解</Text>
</View>
</View>
</View>
)
}
}
const styles = StyleSheet.create({
pan1: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'red',
justifyContent: 'center'
},
pan2: {
height: 200,
justifyContent: 'center',
backgroundColor: 'yellow'
},
pan3: {
height: 100,
backgroundColor: 'blue'
}
})
注意:
release 事件只會(huì)基于最后獲得響應(yīng)權(quán)的元素定義的行為去執(zhí)行。若響應(yīng)權(quán)有變更,會(huì)從那個(gè)元素定義的 grant事件開始。
多指的情況下,將每個(gè)手指的觸摸行為獨(dú)立區(qū)分就可以了。相當(dāng)于一個(gè)并行操作。每個(gè)手指的手勢(shì)行為有它自己獨(dú)立的生命周期。
~ 關(guān)于作者 ~
這是我在簡(jiǎn)書發(fā)表的第一篇 博文,希望大家多多支持給點(diǎn)鼓勵(lì),如有錯(cuò)誤的地方也請(qǐng)第一時(shí)間告訴作者,避免誤人子弟 o((⊙﹏⊙))o.