跳到主要內容

React Native 中的指標事件

·10 分鐘閱讀
Luna Wei
Luna Wei
Meta 軟體工程師
Vincent Riemer
Vincent Riemer
Meta 軟體工程師

今天,我們將分享 React Native 的實驗性跨平台指標 API。我們將介紹動機、運作方式及其對 React Native 使用者的好處。其中包含如何啟用的說明,我們很期待聽到您的回饋!

自從我們分享 我們的多平台願景以來,已經過了一年多,內容是關於在行動裝置以外的平台建置的優勢,以及它如何為所有平台設定更高的標準。在這段期間,我們增加了對 VR、桌上型電腦和網路版 React Native 的投資。由於這些平台上的硬體和互動存在差異,因此引發了 React Native 應如何全面處理輸入的問題。

超越觸控

桌上型電腦和 VR 歷來依賴滑鼠和鍵盤輸入,而行動裝置主要為觸控。隨著觸控螢幕筆記型電腦的出現,以及在行動裝置上支援透過鍵盤和觸控筆進行互動的需求日益增長,這種情況已有所改變。所有這些都是 React Native 觸控事件系統無法處理的。

因此,非樹狀平台的用戶會分支 React Native 和/或建立自訂原生組件和模組,以支援懸停偵測或滑鼠左鍵點擊等重要功能。這種分歧導致屬性冗餘,事件處理常式用於類似目的,但適用於不同平台。這增加了框架的複雜性,並使平台之間的程式碼共用變得繁瑣。基於這些原因,團隊有動機提供跨平台指標 API。

React Native 旨在提供強大且具表現力的 API,以便為多個平台建置應用程式,同時保持平台特有的體驗。設計這樣的 API 具有挑戰性,但幸運的是,在指標領域有先前的技術,React Native 可以加以利用。

放眼網路

網路是一個平台,在擴展到多個平台方面面臨類似的挑戰,同時也考慮到面向未來的設計。全球資訊網協會 (W3C) 的任務是制定標準和提案,以建置可在不同平台和瀏覽器之間互通的網路。

與我們的需求最相關的是,W3C 定義了指標這種抽象形式的輸入的行為。《指標事件》規範建立在滑鼠事件的基礎上,旨在為跨裝置指標輸入提供一組事件和介面,同時在必要時仍允許裝置特定的處理。

遵循指標事件規範為 React Native 使用者帶來許多好處。除了解決先前所述的問題外,它還提高了歷來不必考慮多輸入類型互動的平台的功能。想想將藍牙滑鼠連接到您的 Android 手機,或 Apple Pencil 支援 iPad M2 上的懸停功能。

符合規範也為網路和 React Native 之間的知識共享提供了機會。對圍繞指標事件的網路預期的教育可以雙重服務於 React Native 開發人員。但是,我們也認識到 React Native 的需求與網路不同,而我們對規範的方法是盡力而為,並有充分記錄的偏差,以便明確預期。在將某些網路標準與 減少 API 碎片 在無障礙功能和效能 API 方面,有相關的工作。

移植網路平台測試

雖然指標事件規範提供了 API 的介面和行為描述,但我們發現它不夠具體,無法讓我們放心地進行變更,並將規範作為驗證的依據。但是,網路瀏覽器使用另一種機制來確保合規性和互通性 — 網路平台測試

網路平台測試旨在針對瀏覽器的命令式 DOM API 進行測試 — React Native 不支援它,因為它使用自己的檢視基元。這表示我們無法與瀏覽器共用程式碼測試,而是為 React Native 提供類似的測試 API,使其更容易移植這些網路平台測試。

我們實作了一個新的手動測試框架,我們現在使用它來透過 RNTester 驗證我們的實作。這些測試暫時命名為 RNTester 平台測試,並且仍然相當基本。我們的實作提供了一個 API,用於將測試案例建構為組件本身,這些組件會被渲染,並且結果僅透過 UI 報告。

GIF showing a side by side comparison of the "Pointer Events hoverable pointer attributes test" running in React Native (iOS) on the left, and Web (the original implementation) on the right.

當我們進一步完善指標事件實作的完整性時,這些測試將繼續有所幫助。這些測試也將擴展到測試 Android 和 iOS 以外平台上的指標事件實作。隨著我們的套件中測試數量的增加,我們將尋求自動化執行這些測試,以便我們能夠更好地捕捉實作中的回歸。

運作方式

我們的指標事件實作很大程度上建立在現有的基礎架構之上,用於調度觸控事件。在 Android 和 iOS 上,我們利用相關的 MotionEvent 和 UITouch 事件。事件調度的通用流程如下圖所示。

Diagram of code flow for interpreting Android and iOS UI input events into Pointer Events. On Android, input handlers "onTouchEvent" and "onHoverEvent" fire "MotionEvents" that are interpreted into Pointer Events and through JSI are dispatched to the React renderer. iOS takes a similar path with input handlers "touchesBegan", "touchesMoved", "touchesEnded", and "hovering" interpreting "UITouch" and "UIEvent" into Pointer Events.

以 Android 為例,利用平台事件的通用方法是

  1. 迭代 MotionEvent 的所有指標,並執行深度優先搜尋,以確定每個指標的目標 React 檢視及其祖先路徑。
  2. MotionEvent 的類別對應到相關的指標事件。MotionEventPointerEvent 之間存在一對多關係。在其關係的圖示中,虛線表示如果指標裝置不支援懸停時觸發的事件。

A diagram illustrating the relationship of types of Android MotionEvents into Pointer Events fired. Some pointer events are conditionally fired if pointing device does not support hover. "ACTION_DOWN" and "ACTION_POINTER_DOWN" fire pointerdown and conditionally fire pointerenter, pointerover. "ACTION_MOVE" and "ACTION_HOVER_MOVE" fire pointerover, pointermove, pointerout, pointerup. "ACTION_UP" and "ACTION_POINTER_UP" fire pointerup and conditionally fire pointerout, pointerleave.

  1. 使用來自 MotionEvent 的平台詳細資訊和先前互動的快取狀態來建置 PointerEvent 介面。(例如,button 屬性
  2. 將指標事件從 Android 調度到 React Native 的 核心事件佇列,並利用 JSI 呼叫 dispatchEvent 方法,該方法位於 react-native-renderer 中,它會迭代 React 樹以進行事件的冒泡和捕獲階段。

實作進度

在我們目前實作指標事件規範的進度方面,我們專注於最常見事件的穩固基準實作,這些事件處理諸如按下、懸停和移動之類的事情。

事件

已實作工作中尚待實作
onPointerOveronPointerCancelonClick
onPointerEnteronContextMenu
onPointerDownonGotPointerCapture
onPointerMoveonLostPointerCapture
onPointerUponPointerRawUpdate
onPointerOut
onPointerLeave
資訊

onPointerCancel 已連接到原生平台的「取消」事件,但這不一定對應於網路平台預期它們觸發的時間。

事件屬性

對於上述每個事件,我們也實作了 PointerEvent 物件中預期的大多數屬性 — 儘管在 React Native 中,這些屬性是透過 event.nativeEvent 屬性公開的。您可以在 事件物件的 Flowtype 介面定義中找到所有已實作屬性的列舉。一個值得注意的例外是 relatedTarget 屬性,因為以這種特定方式公開原生檢視參考並非易事。

未來工作和探索

除了上述事件外,還有一些與指標事件相關的其他 API。未來,我們計劃將這些 API 作為此工作的一部分來實作。這些 API 包括

  • 指標捕獲 API
    • 包括元素參考上公開的命令式 API,包括 setPointerCapture()releasePointerCapture()hasPointerCapture()
  • touch-action 樣式屬性
    • 網路使用此 CSS 屬性以宣告方式協商瀏覽器和網站自身事件處理程式碼之間的手勢。在 React Native 中,這可用於協商 View 的指標事件處理程式和父 ScrollView 之間的事件處理。
  • clickcontextmenuauxclick
    • click 是互動的抽象定義,可以透過無障礙功能範例或其他特有的平台互動觸發。

原生指標事件實作的另一個好處是,它將允許我們重新檢視和改進目前僅限於觸控事件且由 Responder、Pressability 和 PanResponder API 在 JavaScript 中處理的各種形式的手勢處理。

此外,我們正在繼續探索為 React Native 主機組件(即 add/removeEventListener)包含 EventTarget 介面的實作,我們認為這將為處理指標互動提供更多使用者領域的抽象。

試用看看

我們的指標事件實作仍處於實驗階段,但我們有興趣從社群獲得對我們所分享內容的回饋。如果您有興趣試用此 API,您需要啟用幾個功能標誌

啟用功能標誌

注意

指標事件僅針對 全新架構 (Fabric) 實作,並且僅適用於 React Native 0.71+,在撰寫本文時,這是一個候選版本。

在您的入口 JavaScript 檔案(預設 React Native 應用程式範本中的 index.js)中,您需要為指標事件啟用 shouldEmitW3CPointerEvents 標誌,並為在 Pressability 中使用指標事件啟用 shouldPressibilityUseW3CPointerEventsForHover

import ReactNativeFeatureFlags from 'react-native/Libraries/ReactNative/ReactNativeFeatureFlags';

// enable the JS-side of the w3c PointerEvent implementation
ReactNativeFeatureFlags.shouldEmitW3CPointerEvents = () => true;

// enable hover events in Pressibility to be backed by the PointerEvent implementation
ReactNativeFeatureFlags.shouldPressibilityUseW3CPointerEventsForHover =
() => true;

iOS 特定

為了確保指標事件從原生 iOS 渲染器發送,您需要在原生應用程式的初始化程式碼(通常為 AppDelegate.mm)中翻轉原生功能標誌。

#import <React/RCTConstants.h>

// ...

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
RCTSetDispatchW3CPointerEvents(YES);

// ...
}

請注意,為了確保指標事件實作可以區分 iOS 上的滑鼠和觸控指標,您需要將 UIApplicationSupportsIndirectInputEvents 新增到您的 Xcode 專案的 info.plist 中。

Android 特定

與 iOS 類似,Android 也有一個功能標誌,您需要在應用程式的初始化中啟用它 — 通常是您的根 React 活動或介面的 onCreate

import com.facebook.react.config.ReactFeatureFlags;

//... somewhere in initialization

@Override
public void onCreate() {
ReactFeatureFlags.dispatchPointerEvents = true;
}

JavaScript

function onPointerOver(event) {
console.log(
'Over blue box offset: ',
event.nativeEvent.offsetX,
event.nativeEvent.offsetY,
);
}

// ... in some component
<View
onPointerOver={onPointerOver}
style={{height: 100, width: 100, backgroundColor: 'blue'}}
/>;

歡迎回饋

如今,指標事件已由我們的 VR 平台使用,並為 Oculus Store 提供支援,但我們也在尋求社群對我們的方法以及我們目前實作的回饋。我們很高興與您分享我們的進一步進展,如果您對這項工作有任何疑問或想法,請加入我們在 指標事件專門討論 中的討論。