Flutter完整開發實戰詳解(二10、 Android PlatformView 和鍵盤問題)

做爲系列文章的第二十篇,本篇將結合官方的技術文檔科普 Android 上 PlatformView 的實現邏輯,而且解釋爲何在 Android 上 PlatformView 的鍵盤老是有問題。java

爲何 iOS 上相對穩定,文中也作了對應介紹。android

文章彙總地址:

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

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

一、爲何有 PlatformView

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

1.一、沒法集成原平生臺控件

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

這意味着默認狀況下 Flutter UI 永遠不會包含 Android Native 的控件,也就是說沒法在 Flutter 中集成如 WebViewMapView 這些經常使用的控件。框架

因此爲解決這個問題,Flutter 建立了一個叫 AndroidView 的控件邏輯, 開發者使用該 Widget 能夠將 Android Native 組件嵌入到 Flutter UI 中編輯器

1.二、AndroidView 的實現

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

VirtualDisplay 相似於一個虛擬顯示區域,須要結合 DisplayManager 一塊兒調用,通常在副屏顯示或者錄屏場景下會用到。VirtualDisplay 會將虛擬顯示區域的內容渲染在一個 Surface 上。學習

如上圖所示,簡單來講就是原生控件的內容被繪製到內存裏,而後 Flutter Engine 經過相對應的 textureId 就能夠獲取到控件的渲染數據並顯示出來

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

1.三、 有其餘能夠實現的方式嗎?

在 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

二、相關問題和解決方法

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

2.一、觸摸事件

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

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

2.1.一、解決方法

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

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

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

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

2.1.二、侷限性

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

  • MotionEvent 的轉化過程當中可能會由於機制的不一樣,存在某些信息沒辦法完整轉化的丟失。

2.二、文字輸入

一般,AndroidView 是沒法獲取到文本輸入,由於 VirtualDisplay 所在的位置會始終被認爲是 unfocused 的狀態

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

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

2.2.一、解決方法

  • 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.二、 Platforview 中的 WebView 鍵盤輸入

在 Android N 以前的版本上 WebView 輸入比較複雜,由於它們具備本身內部的邏輯來建立和設置輸入鏈接,而這些輸入鏈接並無徹底遵循 Android 的協議。在 flutter_webview 插件中,還須要添加其餘解決方法以便在能夠在 WebView 啓用文本輸入。

2.2.三、侷限性

三、總結

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

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

webview_flutter 的鍵盤支持也還沒有準備好用於生產,由於 Webview 中的鍵盤支持目前還處於實驗性的階段。

因此到這裏相信你應該知道,爲何 Flutter 中的 PlatforView 在 Android 上如此之難兼容,而且鍵盤輸入問題會那麼多坑了

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

資源推薦

相關文章
相關標籤/搜索