React Native - PanResponder - 捕獲冒泡機(jī)制詳解

最近的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 )

image

它的手勢(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ì)縮小。

image

二、基于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'
  }
})

react-native panResponder傳送門

注意:

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.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • ??JavaScript 與 HTML 之間的交互是通過(guò)事件實(shí)現(xiàn)的。 ??事件,就是文檔或?yàn)g覽器窗口中發(fā)生的一些特...
    霜天曉閱讀 3,678評(píng)論 1 11
  • React Native 進(jìn)階(一)--嵌入到Android原生應(yīng)用中、組件的生命周期、顏色、圖片、觸摸事件 嵌入...
    呼呼哥閱讀 1,422評(píng)論 0 5
  • 好奇觸摸事件是如何從屏幕轉(zhuǎn)移到APP內(nèi)的?困惑于Cell怎么突然不能點(diǎn)擊了?糾結(jié)于如何實(shí)現(xiàn)這個(gè)奇葩響應(yīng)需求?亦或是...
    Lotheve閱讀 59,425評(píng)論 51 604
  • 本文主要講解iOS觸摸事件的一系列機(jī)制,涉及的問(wèn)題大致包括: 觸摸事件由觸屏生成后如何傳遞到當(dāng)前應(yīng)用? 應(yīng)用接收觸...
    baihualinxin閱讀 1,266評(píng)論 0 9
  • 1. 一位網(wǎng)友跟我說(shuō):“我從不敢去北京豐臺(tái)區(qū)的造甲村,因?yàn)椴恢览锩娴臇|西哪個(gè)是真的?” 2. 都說(shuō)外面的世界很精...
    AprilFriday閱讀 153評(píng)論 0 0

友情鏈接更多精彩內(nèi)容