隨著新板型規格超越觸控螢幕,Compose 的輸入系統也與時俱進,以因應挑戰。其分層架構奠定了基礎,可完美支援從進階觸控板手勢到新穎硬體輸入等各種功能。
本課程提供清楚的架構細目和實際程式碼範例,帶您瞭解輸入事件的生命週期,從 OS 平台層一路到 Compose 手勢偵測工具和 UI 元件。您將瞭解如何追蹤整個堆疊中的事件、為新興輸入類型套用重要架構模式,以及在自己的應用程式中自信地偵錯複雜的手勢互動。
performTrackpadInput 等新方法。這些 API 提供熟悉的測試介面,可驗證應用程式是否正確處理非觸控裝置。轉錄稿
大家好!
希望您在 Android 開發人員高峰會上收穫滿滿,也感謝您參加我們的講座。
我是 Compose 團隊的軟體工程師 Alex Vanyo,今天與我一起的是 Compose 團隊的另一位軟體工程師 Jeremy。
我們一直致力於強化 Jetpack Compose 的輸入系統,今天將說明如何將系統擴充至觸控螢幕以外的裝置,以便輕鬆支援非觸控輸入。
廣義來說,輸入是指使用者與應用程式互動的任何方式。
提到行動應用程式,您可能首先會想到觸控螢幕,但這絕對不是唯一的輸入類型。
其他類型的輸入方式包括實體鍵盤、滑鼠、觸控板、觸控筆、控制器和旋轉裝置。
我們也看到新的輸入類型,例如智慧眼鏡上的觸控板、XR 中的眼球和手勢追蹤,以及 Wear 上的手指和手腕手勢。
如果想進一步瞭解,還有其他類型的輸入方式,例如用於文字編輯的軟體輸入法編輯器、麥克風、相機和其他裝置感應器。
您必須預期應用程式會與各種輸入類型搭配使用。
其中一個原因是,應用程式執行的裝置種類越來越多,且這些裝置的適應性也越來越高。
裝置板型規格可能有最常見的輸入法,但請務必不要假設特定裝置板型規格代表使用特定輸入類型。
雖然 Chromebook 內建觸控板和鍵盤,但這並不代表這些輸入類型是該板型規格獨有。
您應該預期實體鍵盤、觸控板和滑鼠也會在手機、平板電腦和電視上使用,有些電視甚至有觸控螢幕。
預期並支援多種輸入類型的第二個重要原因,是無障礙功能。
允許使用者使用自選的輸入裝置,並允許他們視情況切換輸入裝置,是提高應用程式無障礙程度和可用性的正確做法。
此外,根據管轄區的不同,法律可能要求應用程式必須針對這些輸入類型 (尤其是鍵盤和滑鼠等) 採取特定、相容的行為。
簡而言之,無論板型規格為何,您都應建構對不同輸入類型的支援。
接著,我們來看看 Jetpack Compose 如何協助應用程式滿足這些使用者需求。
一般來說,Compose 的理念是,這些不同的輸入類型應能與標準元件搭配使用,且不需進行任何特殊操作。
接著,您應該要有能自訂及調整行為的工具,以便視需要進行調整。
因此,除了這些初始輸入類型外,隨著基礎平台支援更多輸入類型,我們也希望在 Compose 元件中加入對這些類型的支援,只要更新 Compose 版本,就能改善應用程式的行為。
我們將在本次演講中介紹兩個這類範例,兩者都是 Compose 最近新增的功能。
巧合的是,這兩者都是類似觸控板的裝置,但使用方式截然不同。
首先,我們來看看最近在筆電式觸控板上進行的一些工作。
觸控板是以指標為基礎的輸入裝置。
具體來說,使用觸控板時,您會取得二維座標。
但與觸控螢幕不同的是,觸控板上的手指座標不會直接位於 UI 的座標空間中。
如果是筆電觸控板,則會轉譯為控制游標。
在這個轉譯過程中,會進行許多複雜的平台轉換,例如游標加速和手掌誤觸抑制。
因此,在 Compose 端,您會看到與游標位置對應的指標,其行為通常類似滑鼠輸入。
我們從 Android 平台取得懸停資訊、點擊 (包括次要或第三次點擊),並將所有這些資訊包裝成 MotionEvent。
為了瞭解我們如何改善最新版本 Compose 的行為,首先來概略瞭解 Compose 如何更密切地處理這些指標事件。
我會將這個過程分為六個階段,並逐一說明。
第一階段是將 MotionEvents 移至 Compose。
因此,Compose 實際上只是一個檢視畫面:一個非常複雜、能執行許多操作的檢視畫面,但它只是一個檢視畫面,這意味著它需要在整個 Android 系統中運作,才能處理指標輸入。
因此,在 Compose 中,這表示 Compose 位於一般活動的一般視窗中,會取得 MotionEvent:Android 平台的 MotionEvent。
接著,Compose 必須處理這些事件,並在 Compose 中執行您想執行的所有指標輸入相關操作。
第一個階段是將這些 MotionEvent 轉譯為 Compose 中與平台無關的內部表示法,我們稱為 PointerInputEvent。
這是內部類型,因此您不會直接看到。
如果想查看這裡發生的所有詳細資料,可以查看 MotionEventAdapter,但我們在這裡會略過這些詳細資料。
現在,我們在 Compose 中有了與平台無關的 MotionEvent 表示法。
接下來,輸入通常會隨著時間發生。
您會陸續收到許多輸入事件,因此實際瞭解一段時間內發生的情況非常實用。
手勢、速度追蹤、斜率偵測等,都需要瞭解每個時間點發生的情況。
因此,這個處理管道的下一個階段是加入時間資訊,並根據連續的指標事件進行一些預先處理。
在程式碼中,您可以將其視為採用目前的 PointerInputEvent,並與前一個事件發生的情況結合,然後建立時間感知 InternalPointerEvent。
顧名思義,這也是另一種內部類型,但您可以將其視為不同指標 ID 與指標輸入變更的對應,而 PointerInputChanges 是公開類型。
這是第一個。
思考不同指標 ID 時,您可能會注意到,既然我們知道追蹤時間,也可以追蹤指標 ID 的出現和消失時間。
不只一個。
如果觸控螢幕上有多根手指,可能同時會有多個指標來來去去,我們需要處理這種情況。
同樣地,如果您想深入瞭解細節,可以查看 PointerInputChangeEventProducer,它會執行所有這些操作,但我們會略過。
還有很多內容要介紹。
現在,我們有了輸入內容的 Compose 表示法,其中包含時間資訊。
下一步是實際找出如何處理該事件。
我們該寄到哪裡?
這時就要進行命中路徑測試。
假設我們傳送 MotionEvent,而這個 ComposeView 填滿整個視窗,甚至是整個螢幕。
我們需要找出傳送目的地。
Compose 只是檢視畫面,但 Compose 內有許多內容,包括各種節點、按鈕、互動項目,因此我們必須在 Compose 中完成所有工作,找出要將特定位置發生的事件傳送至何處,並找出實際對該事件感興趣的節點。
這些版面配置節點有座標、邊界和形狀,因此我們需要找出是否有任何節點被點擊。
但這也不是最通用的觀點。
我們喜歡將可互動的項目放在其他可互動的項目中,例如在清單中加入按鈕。
我們經常這麼做。
因此,這裡的一般情況是,特定輸入事件會建立完整的命中路徑,該路徑會通過輸入事件座標命中的每個可互動節點。
因此,在這個例子中,巢狀階層中會有三個。
因此,我們有 Hit Path,但這還不夠。
還記得我說過,我們有多個指標輸入事件,以及多個可能按下的指標嗎?
我們需要完整的命中路徑樹狀結構。
如果有多個指標,就不會只有一個命中路徑。
因此,下一個階段是建立及更新這個命中路徑樹狀結構。
接著,我們來進一步瞭解實際情況。
左側是視覺化呈現的節點,左下方則是階層式表示法,顯示哪些節點位於其他節點內,以及哪些節點是彼此的上層或下層節點。
當指標向下移動時,系統會追蹤該命中路徑 (即通過樹狀結構的完整路徑),然後取得該特定指標的命中路徑。
當我們取得第二個指標時,這個命中路徑就會變成樹狀結構。
它可能與另一個網路的某些節點交集,但也可能與其他節點互動。
右側是我們目前維護的命中路徑樹狀結構。
指標可能會隨時變動,因此我們需要自行維護這項資訊,確保能取得目前對每個進行中事件感興趣的完整節點清單。
現在我們來做個小測驗
現在,我們已為這些輸入事件維護命中路徑樹狀結構,這表示我們已進入下一個階段。
現在,我們知道要將這些指標輸入事件傳送到何處,以及哪些節點對每個特定事件感興趣。
瞭解這點後,我們就能進行座標轉換。
如果節點在本機座標空間內接收輸入事件,您就不必自行進行大量座標數學運算,這點非常實用。
因此,一旦我們知道這一點,就可以取得指標輸入事件,並將其與感興趣的節點座標結合,然後最終建立公開的 PointerEvent,並將其分派給所有人和指標輸入修飾符。
最後,在第 6 階段,我們終於要實際傳送事件。
這是指標輸入事件,事件發生後會傳送至節點,讓節點執行所需操作。
我們分三次完成這項作業。
包括初步、主要和最終階段。
之所以有三個不同的階段,是為了讓節點彼此通訊,並傳達對特定指標輸入事件特定部分的興趣,然後在分派過程中取用這些事件的不同部分。
因此,第一個階段 (初始階段) 會從祖系到子系。
舉例來說,如果某個外部版面配置節點可能是捲動容器,且知道自己已開始捲動,並想停止所有指標事件到達任何子項,這項功能就非常實用。
這樣一來,在初始傳遞時,系統就會允許它使用,然後子項就不會再收到任何事件。
第二個票證是主要票證。
這是唯一從子系到祖系的關係。
顧名思義,這是執行作業的主要階段。
舉例來說,如果後續事件會消耗按下事件,並開始處理點擊,那麼在巢狀可點擊項目中,後續事件就有機會在任何父項之前執行這項操作。
最後一個階段 (最終階段) 與初始階段相同,都是從祖系到子系。
這樣一來,子項就能通知父項,表示父項已取用部分事件。
因此,如果子項開始處理點擊,但父項開始捲動,子項可以偵測到父項的動作,並取消已開始處理的點擊。
以上就是三個階段和六個步驟。
這些是游標輸入系統上所有內容的基礎。
所有手勢處理、可捲動、可拖曳、可點擊和任何自訂指標輸入,都是以這個基礎為基礎。
現在,我們可以進一步討論 Compose 如何改善觸控板行為支援。
因此,在 Compose 1.12 之前,觸控板輸入事件會回報為觸控指標類型,因為使用者是以手指實際與觸控板互動來控制游標。
不過,使用者期望這種輸入類型的運作方式更像實體滑鼠。
因此,我們將指標類型從 PointerType.Touch 變更為 PointerType.Mouse。
這項異動可讓使用者在任何查看這項資訊的元件上,享有更一致的滑鼠和觸控板體驗。
舉例來說,工具提示行為:您可能希望觸控板和滑鼠的工具提示行為相同。
我們對觸控板所做的第二項重大變更,與觸控板上的多指手勢有關。
先前,Compose 會透過 Android 平台巧妙但有點詭異的假手指概念。
我們來看看這代表什麼意思。
在觸控板上用兩指平移時,Android 會產生一個虛擬觸控螢幕手指,在游標所在位置按下。
接著,當你在觸控板上移動手指時,觸控螢幕上的假手指會移動正確的距離和方向,模擬所需行為,然後放開。
因此,在觸控板上向上移動兩指,會轉譯為觸控螢幕上的單一假手指,也會向上移動,進而導致向下捲動。
有趣的是,如果你和我一樣使用反向觸控板捲動功能,就會希望捲動方向相反。
做法是翻轉假手指的方向。
現在,在觸控板上向上移動兩指,會轉譯為在觸控螢幕上向下移動一根假手指,進而導致向上捲動,與先前的結果相反。
資源調度也類似。
在觸控板上使用雙指縮放時,Android 會產生兩根虛擬觸控螢幕手指,並根據手勢靠近或分開,模擬放大或縮小的行為。
因此,自 Compose 1.12 (平移) 和 Compose 1.13 (縮放) 起,我們現在會攔截這些假手指。
我們不會直接傳送平台提供的事件,而是會重新解讀為新的指標事件類型:PanStart、PanMove 和 PanEnd 用於平移,ScaleStart、ScaleChange 和 ScaleEnd 用於縮放。
我們會在先前介紹的 Android 到 Compose 事件轉換階段執行這項操作。
因此,我們會將假手指事件轉換為適當的類型,然後透過這個平台定義的手勢,傳送至與游標對應的命中路徑。
接著來看看這項功能的重要性。
為什麼要這麼做?
假設這個方塊是一張精美的圖片,我們想要放大。
在觸控板上使用兩指縮放時,其中一指可能會錯過元素。
其命中路徑不會與圖片相交,且會從邊界外開始。
因此,這會導致圖片捲動,而不是縮放。
如果圖片外還有其他可捲動的元素,可能也會一起捲動。
這不符合使用者需求。
他們希望擴大業務規模。
因此,我們現在不會傳送這些假手指,而是只將縮放手勢傳送至游標位置下方的元素,這樣就能產生正確的行為。
無論游標位於何處,或與要縮放的元素邊緣有多近,現在都能正確縮放。
好消息是,如果您使用這些基本建構元素,可捲動和可轉換等元件預設會使用這些重新解讀的輸入事件,為觸控板輸入提供更高傳真的行為。
如果您使用以這些元件為基礎建構的任何元件,只要將 Compose 更新至最新版本,就能享有更優異的行為。
如果您正在編寫自訂指標輸入修飾符,建議您查看這些新的 API,並確保支援這些 API,為使用者提供最佳體驗,無論他們使用哪種輸入裝置。
當然,我們也新增了測試 API,您也可以檢查應用程式是否正確處理所有這些不同的輸入類型。
因此,performTrackpadInput 看起來與現有的滑鼠和觸控測試 API 非常相似。
onNodeWithContentDescription("Photo")
.performTrackpadInput {
click(center)
pan(Offset(0f, 100f))
scale(0.9f)
}無論使用者在裝置上使用何種輸入裝置,您都可以使用上述所有方法,驗證應用程式是否正常運作。
希望這能讓您更清楚瞭解指標事件如何從觸控板上的手指,一路傳遞到應用程式。
接下來,我將交棒給 Jeremy,他會介紹觸控板的另一種用法。
謝謝。
現在我們來做個小測驗
大家好,我是 Jeremy Walker。
我經常處理輸入內容,因此想與您分享我們在 Compose 輸入內容方面的一些新功能,以及這些功能對您的影響。
尤其是這種間接輸入類型,這是因為智慧眼鏡這種新板型規格而產生的新輸入方式。
因此,當你從眼鏡取得輸入內容時,可以透過幾種方式:你可以看著某個東西、使用語音、使用手勢,但我會特別談論觸控板。
這些與 Alex 提到的有點不同,因為它們位於頭部兩側,垂直、非常寬且極短。
因此,當我們思考這項功能在 Compose 中的運作方式時,如果想像眼前有一個小按鈕,而您移動游標嘗試觸碰它,這並不是很好的體驗,對吧?
那麼,我們要如何支援這項功能?
提醒您,這是 Glasses UI 的外觀。
這款眼鏡採用獨特設計,可將內容完美疊加在視野中。
但我們盡量保持簡單。
從這個範例可以看到,這是一份清單。
聚焦於中央的特定元素。
我們真正想做的是什麼?
我們想選取這個項目,或移至上一個或下一個項目。
不一定要提供螢幕位置。
這個稍微複雜一點,但基本上有三個選項:掛斷、取消靜音或關閉視訊。
但實際上,您需要選取該項目,或移至其他項目並選取。
因此,我們認為不需要直接對應到畫面上的位置,或是在這個情況下,你的眼睛。
這就是實際的樣子。
舉例來說,您可以在這裡看到,我只是在元素之間移動,這是在眼鏡 UI 上,這符合我們的需求。
輕觸即可。
因此,我們研究了 Android,思考有哪些現有功能可以支援這項功能。
如果各位有,抱歉。
這裡會跳過。
讓我回去。
有了
現在我們來做個小測驗
如果各位曾使用過這類裝置,並與 Android 裝置連結,就會知道可以輕鬆在不同裝置的 UI 元素之間切換。
舉例來說,如果遙控器已連結 Google TV,畫面上會顯示一整排電影,你可以按下方向鍵,找到想看的電影,然後按下「確定」按鈕選取。
如果你有遊戲選單和這個控制器,可以使用 D-pad 或搖桿移動到 UI 元素 (例如選單),然後選取該元素。
鍵盤也一樣。
這與我們在 Compose 中想要達成的效果相符。
再次提醒,以下是相關資訊,方便您瞭解。
如果再往前追溯,其實有一種觸控板類型是與電視配對,並使用一種稱為導覽觸控的功能。
這個做法完全不涉及 X 和 Y 座標,與螢幕完全無關。
僅用於偵測手勢。
因此,向右滑動就像是按下「喜歡」或向右箭頭。
向左滑動,則會向左、向上和向下移動。
這樣一來,我們就能大致取得所需資訊。
您只需要使用 x 和 y 進行速度追蹤,即可找出拖曳和滑動等動作,因此這能提供我們在 Compose 上開始所需的大部分內容。
這並非精確的對應,因為它有點對應到電視,所以我們需要確保不走電視路線,不弄亂電視,所以我們需要確保將它們分開,但這是我們稍後會討論的事情。
但至少能做為起點。
這樣我們就能使用這類觸控導覽。
好的。
API 設計會是什麼樣子?
在 Compose 中,最常見或最普遍的輸入方式是觸控,對吧?
因此,我們在這方面做了很多工作。
因此,我們必須設法找出兩者之間的關聯。
提醒您,PointerEvent 的結構如下。
您可以在這裡看到類型,也就是向上移動的向下類型,然後您有這個 PointerInputChange,這只是一個清單,您可以將其視為手指。
如果你放下三根手指,清單中就會有三個項目。
因此我們希望它看起來像這樣。
這不是 UI 模組,而是比多數開發人員處理輸入時使用的模組更低階的模組。
這些服務會位於基礎層上方。
所有手勢偵測工具都在這裡。
因為身為開發人員,您不會在意這些細微的輸入。
您只需要知道是否發生拖曳、點擊或滑動,然後根據該動作執行操作。
因此,我們提供了一堆程式碼,可為您執行這項操作,並由這些程式碼在幕後提供支援。
因此,我們開始思考,如何讓基礎中的現有程式碼盡可能輕鬆地支援這些新類型。
因此,我們建立了一個看起來幾乎相同的新類型。
只是稱為間接。
「間接」是指不直接觸碰螢幕。
因此,這項功能會讓您知道,這項功能即將推出,請不要從觸控的角度思考。
請注意,我們在這裡只是處理手勢,以及在 UI 元素之間瀏覽。
類型相同。
其中包含手指清單。
在本例中,是 IndirectPointerInputChange。
我們確實這麼做了,而且效果很好。
但我們還發現另一個項目,稱為「主要方向動作軸」。
這聽起來有點複雜,但等我說明後,你就會明白,因為它的由來很有趣。
這項功能是如何誕生的?
同樣地,眼鏡側邊有觸控板,寬度較寬,長度較短。
因此,我們當時的想法是,既然有了觸控導覽,
所以我們可以左右滑動。
效果很好。
不過,如果仔細審視現有的觸控板
向左或向右移動時,觸控板上的 UI 內容也會向左或向右移動。
如果向上或向下移動,清單就會向上或向下捲動。
如果戴著眼鏡,就像這樣,其實還不錯,對吧?
但如果這樣做,體驗就不太好。
我們發現最自然的動作顯然是輕觸,但要沿著眼鏡側邊滑動。
因此,我們對使用者進行了大量研究,發現他們實際上預期在水平滑動時,內容不會水平移動,而是垂直移動。
你可以想像一下,當你在這裡滑動時,如果面前有一份清單,你可以向前瀏覽清單,也可以向後瀏覽清單。
因此,我們採用二維輸入,並嘗試以一維方式思考。
這就是主要動作軸概念的由來,你可以水平移動,它會垂直移動。
因此,您可以在裝置上指定。
你可以說,這不是一般的觸控板。
我想將主要方向設為水平,這也是我唯一想沿著移動的主軸。
這就是這項功能的由來。
好的。
因此,我們對這方面很有信心。
Compose 會處理所有作業。
您也可以採用這種直向實作方式。
你可以考慮一下。
因此,我們對應的方式是,現在您有了水平定義,我們只有前進和後退。
我們沒有上、下、左、右。
我們只有前進和後退。
如果您熟悉焦點,可以使用下一個和上一個來對應。
現在,我們只要移至下一個和上一個。
這對直向影片來說非常實用。
而且更棒的是,
這項功能可直接用於橫向模式,對吧?
因為你只有一堆聚焦項目。
只要前往下一頁或上一頁,兩者都能正常運作。
因此,在 Compose 中,我們定義並解決了這個問題,但如果裝置本身能說出,因為它知道它是否是奇怪的形狀,並且知道它是否只需要這個主軸,但它沒有辦法定義。
因此,我們必須將這個概念帶回平台,並說,好吧,我們需要在平台中定義這個概念,以便將其標準化,而這些裝置本身可以指定它,這樣 Compose 就可以完全支援它。
因此,我們在 Compose 中進行開發,並在 Compose 中向前和向後開發,並將其帶入。
這正是我們採取的做法。
我們建立了這個新 API,讓您或裝置可以指定。
InputDevice.ViewBehavior
.getPrimaryDirectionalMotionAxis()還記得我先前談到觸控輸入或觸控導覽時,曾說過這主要是用於電視。
我們不希望破壞這些實作。
因此,我們新增了另一個欄位,用來指定這種新的玻璃輸入類型。
好的。
這對您有什麼影響?
這是手勢偵測工具的更具體版本。
// Glimmer Library
Box(
modifier =
Modifier.fillMaxSize()
.onIndirectPointerGesture(
enabled = true,
onSwipeForward = { /* onSwipeForward */ },
onSwipeBackward = { /* onSwipeBackward */ },
onClick = { /* onClick */ },
)
.focusTarget()
) {
// App()如果您使用 Foundation,則可直接搭配手勢偵測工具使用。
我們已為您完成這項作業,您不必採取任何行動。
但如果你想為眼鏡製作應用程式,可以使用我們的 Glimmer 程式庫,然後我們有一個特殊的手勢偵測器,它只會給你,你知道,所以你不必將它們結合起來,你可以只使用這一個,獲得點擊,前進框架,和後退。
好的。
我們已介紹 API 在平台和 Compose 中的運作方式。
測試
這就是觸控測試的畫面。
@Test
clickTest {
rule.onNodeWithTag(defaultTag)
.performTouchInput
{ click(center) }
}只要說出「performTouchInput」,然後做出手勢即可。
手勢有很多種,我們需要建構所有小事件,才能讓手勢發生。
概略運作方式為何?
您有 Compose UI 模組和 UI 測試模組,兩者會透過 MotionEvent 進行通訊,至少在輸入方面是如此。
此外,UI 測試模組也使用佇列機制,全部以 MotionEvent 為基礎。
為什麼要告訴你這些?
因為您無法以程式輔助方式建立類似間接輸入事件的 MotionEvent,這對我來說是件好事,因為這表示我必須重寫模組和佇列系統之間的所有通訊。
但我做到了。
這對您有什麼影響?
如果你習慣觸控輸入測試,這就是間接輸入的結果。
只要在名稱中加入「indirect」即可。
@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput (
IndirectPointerEventPrimaryDirectionalMotionAxis.X,
inputDeviceSize,
)
{ click(center) }
}現在,我確實傳遞了一些額外參數。
您可能認得第一個,那是指定水平位置的地方。
輸入大小則顯然是觸控板的大小。
這項設定不會造成太大影響。
因此,如果查看原始碼,您會發現我只是編寫了擴充功能函式,看起來就像這樣。
@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput
{ click(center) }
}好的。
這應該能讓大家瞭解,我們為了在鏡框或智慧眼鏡上支援觸控板,做了多少努力。
但我們正在進行這些,我也在進行這些眼部和手勢追蹤的練習,所以還有很多工作要做。
總而言之,我希望大家記住什麼?
onNodeWithContentDescription("Photo")
.performTrackpadInput {
click(center)
pan(Offset(0f, 100f))
scale(0.9f)
}InputDevice.ViewBehavior
.getPrimaryDirectionalMotionAxis()// Glimmer Library
Box(
modifier =
Modifier.fillMaxSize()
.onIndirectPointerGesture(
enabled = true,
onSwipeForward = { /* onSwipeForward */ },
onSwipeBackward = { /* onSwipeBackward */ },
onClick = { /* onClick */ },
)
.focusTarget()
) {
// App()@Test
clickTest {
rule.onNodeWithTag(defaultTag)
.performTouchInput
{ click(center) }
}@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput (
IndirectPointerEventPrimaryDirectionalMotionAxis.X,
inputDeviceSize,
)
{ click(center) }
}@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput
{ click(center) }
}