Flutter完整開發實戰詳解(二十、AndroidPlatformView和鍵盤問題)

NO IMAGE

作為系列文章的第二十篇,本篇將結合官方的技術文檔科普 Android 上 PlatformView 的實現邏輯,並且解釋為什麼在 Android 上 PlatformView 的鍵盤總是有問題。

為什麼 iOS 上相對穩定,文中也做了對應介紹。

文章彙總地址:

Flutter 完整實戰實戰系列文章專欄

Flutter 番外的世界系列文章專欄

1、為什麼有 PlatformView

因為 Flutter 的實現在概念上類似於 Android 上的 WebView,Flutter 是通過將 Widget Tree 轉化為紋理後通過 Skia 實現控件繪製,這造就了優秀的跨平臺效果的同時,也帶來了不可逆的兼容問題。

1.1、無法集成原生平臺控件

這就像 WebView 一樣,Flutter UI 不會轉換為 Android 控件,而是由 Flutter Engine 使用 Skia 直接在 SurfaceView 上渲染出來

這意味著默認情況下 Flutter UI 永遠不會包含 Android Native 的控件,也就是說無法在 Flutter 中集成如 WebViewMapView 這些常用的控件。

所以為解決這個問題,Flutter 創建了一個叫 AndroidView 的控件邏輯, 開發者使用該 Widget 可以將 Android Native 組件嵌入到 Flutter UI 中

1.2、AndroidView 的實現

AndroidView 這個 Widget 需要和 Flutter 相結合才能完整顯示:在 Flutter 中通過將 AndroidView 需要渲染的內容繪製到 VirtualDisplays
,然後在 VirtualDisplay 對應的內存中,繪製的畫面就可以通過其 Surface 獲取得到

VirtualDisplay 類似於一個虛擬顯示區域,需要結合 DisplayManager 一起調用,一般在副屏顯示或者錄屏場景下會用到。VirtualDisplay 會將虛擬顯示區域的內容渲染在一個 Surface 上。

Flutter完整開發實戰詳解(二十、AndroidPlatformView和鍵盤問題)

如上圖所示,簡單來說就是原生控件的內容被繪製到內存裡,然後 Flutter Engine 通過相對應的 textureId 就可以獲取到控件的渲染數據並顯示出來

通過從 VirtualDisplay 輸出中獲取紋理,並將其和 Flutter 原有的 UI 渲染樹混合,使得 Flutter 可以在自己的 Flutter Widget tree 中以圖形方式插入 Android 原生控件。

1.3、 有其他可以實現的方式嗎?

在 iOS 平臺上就不使用類似 VirtualDisplay 的方法,而是通過將 Flutter UI 分為兩個透明紋理來完成組合:一個在 iOS 平臺視圖之下,一個在其上面

所以這樣的好處就是:需要在“iOS平臺”視圖下方呈現的Flutter UI,最終會被繪製到其下方的紋理上;而需要在“平臺”上方呈現的Flutter UI,最終會被繪製在其上方的紋理。它們只需要在最後組合起來就可以了

通常這種方法更好,因為這意味著 Android Native View 可以直接添加到 Flutter 的 UI 層次結構中。

但是,Android 平臺並不支持這種模式,因為在 iOS 上框架渲染後系統會有回調通知,例如:當 iOS 視圖向下移動 2px 時,我們也可以將其列表中的所有其他 Flutter 控件也向下渲染 2px

但是在 Android 上就沒有任何有關的系統 API,因此無法實現同步輸出的渲染。如果強行以這種方式在 Android 上使用,最終將產生很多如 AndroidView 與 Flutter UI 不同步的問題

有關此替代方法的詳細討論,詳見 flutter.dev/go/nshc

2、相關問題和解決方法

儘管前面可以使用 VirtualDisplay 將 Android 控件嵌入到 Flutter UI 中 ,但這種 VirtualDisplay 的介入還有其他麻煩的問題需要處理。

2.1、觸摸事件

默認情況下, PlatformViews 是沒辦法接收觸摸事件

因為 AndroidView 其實是被渲染在 VirtualDisplay 中 ,而每當用戶點擊看到的 "AndroidView" 時,其實他們就真正”點擊的是正在渲染的 Flutter 紋理 。用戶產生的觸摸事件是直接發送到 Flutter View 中,而不是他們實際點擊的 AndroidView

2.1.1、解決方法

  • AndroidView 使用 Flutter Framework 中的點擊測試邏輯來檢測用戶的觸摸是否在需要特殊處理的區域內。

類似可見:《Flutter完整開發實戰詳解(十三、全面深入觸摸和滑動原理)》

  • 當觸摸成功時會向 Android embedding 發送一條消息,其中包含 touch 事件的詳細信息。

  • Android embedding 中,該事件的座標最後會匹配到 AndroidViewVirtualDisplay 中的座標,然後會創建一個 MotionEvent 用於 描述觸摸的新控件,並將其轉發到內部 VirtualDisplay 中真實的 AndroidView 中進行響應。

2.1.2、侷限性

  • 該實現邏輯會將新的 MotionEvent 直接分發給 AndroidView ,如果這個 View 又派生了其他視圖,那麼就可能會出現觸摸信息被髮送到錯誤的位置。

  • MotionEvent 的轉化過程中可能會因為機制的不同,存在某些信息沒辦法完整轉化的丟失。

2.2、文字輸入

通常,AndroidView 是無法獲取到文本輸入,因為 VirtualDisplay 所在的位置會始終被認為是 unfocused 的狀態

Android 目前不提供任何 API 來動態設置或更改的焦點 WindowFlutterfocusedWindow 通常是實際持有“真實的” Flutter 紋理和 UI ,並且對於用戶直接可見。

InputConnections(如何在 Android 中 輸入文本)在 unfocused 的 View 中通常是會被丟棄

2.2.1、解決方法

  • Flutter 重寫了 checkInputConnectionProxy 方法,這樣 Android 會認為 Flutter View 是作為 AndroidView 和輸入法編輯器(IME)的代理,這樣 Android 就可以從 Flutter View 中獲取到 InputConnections 然後作用於 AndroidView 上面。

  • 在 Android Q 開始 InputMethodManager(IMM)改為每個 Window 自己實例化而不是全局單例。因此之前幼稚的“設置代理”的模式在 Q 開始不起作用。為了進一步解決這個問題,Flutter 創建了一個 Context 的子類, 該子類返回的內容與 Flutter View 中的 IMM 相同,這樣就不會需要在查詢 IMM 時需要返回的真實的 Window。這意味著當 Android 需要 IMM 時,VirtualDisplay 仍然會使用 Flutter View 的 IMM 作為代理。

  • 當要求 AndroidView 提供 InputConnection 時,它會檢查 AndroidView 是否確實是輸入的目標。如果是,那 AndroidView 中的 InputConnection 將被獲取並返回給 Android

  • Android 認為 Flutter View 是 focused 且可用的,因此 AndroidViewInputConnection 可以成功被獲取並使用。

2.2.2、 Platforview 中的 WebView 鍵盤輸入

在 Android N 之前的版本上 WebView 輸入比較複雜,因為它們具有自己內部的邏輯來創建和設置輸入連接,而這些輸入連接並沒有完全遵循 Android 的協議。在 flutter_webview 插件中,還需要添加其他解決方法以便在可以在 WebView 啟用文本輸入。

2.2.3、侷限性

3、總結

PlatformView 的實現模式增加了 Flutter 的生命力和活力,但是相對的也引出了很多問題,比如 #webview-keyboard#webview#platform-views 相關的 issue 專題高居不下,並且如 webview_flutter 插件的文檔所述:

該插件依賴 Flutter 的新機制來嵌入 Android 和 iOS 視圖。由於該機制當前處於開發人員預覽中,因此該插件也應被視為開發人員預覽。

webview_flutter 的鍵盤支持也尚未準備好用於生產,因為 Webview 中的鍵盤支持目前還處於實驗性的階段。

所以到這裡相信你應該知道,為什麼 Flutter 中的 PlatforView 在 Android 上如此之難兼容,並且鍵盤輸入問題會那麼多坑了

自此,第二十篇終於結束了!(///▽///)

資源推薦

Flutter完整開發實戰詳解(二十、AndroidPlatformView和鍵盤問題)

相關文章

🔥Webpack插件開發如此簡單!

http常被問到的知識總結

刪庫了,我們一定要跑路嗎?

Flutter混合開發實戰問題記錄(五)1.9.1hotfix打包aar差異