由於 iOS 26 的鍵盤變化,Flutter 又要重構鍵盤區域邏輯

在之前的 《iOS 26 鍵盤變化可能帶來大量底層改動》 我們就聊過,如下圖所示,是由於 iOS 26 系統鍵盤增加了「半透明」之後帶來的問題,Flutter 在鍵盤後面那一層在某些場景下沒有正確渲染內容,導致鍵盤半透明區域透出來的不是底下 BottomSheet 的真實內容,而是一整塊黑色區域。

因為過去 Flutter 的 Scaffold.resizeToAvoidBottomInset 預設是 true,所以鍵盤彈出時,Scaffold 會把 body 縮到鍵盤上方,鍵盤區域一般由 Scaffold.backgroundColor 填充,這個模式在以前的 iOS 和 Android 上問題不大,因為鍵盤一直都是一個不透明矩形,就算後面顏色不對,使用者也看不到。

所以,過去 Flutter 的鍵盤避讓模式預設鍵盤是不透明、非圓角的,但 iOS 26 的 Liquid Glass 鍵盤變成半透明和圓角以後,問題就出現了,針對問題其實可以暫時選擇設定 UIDesignRequiresCompatibility = YES 來解決,或者改用 Dialog 來繞過場景,但是問題還是需要解決,所以官方認為有必要重構實作。

是不是太抽象了?我們可以透過更具體的例子來理解,原本 Flutter 裡的 Scaffold.resizeToAvoidBottomInset 預設是 true,所以當鍵盤彈出時,Scaffold 會根據 MediaQueryData.viewInsets.bottom 把 body 縮到鍵盤上方,正常來說應該是下面這樣的:

但是上面看起來沒問題,只是剛好場景沒遇到問題,如果是下面這種場景,在 iOS 26(圖 2)UI 就會變得很奇怪,因為正常互動上,他應該是圖 3 那種情況:

很明顯問題就出現在鍵盤下又多了一層的情況下,透明色和圓角就會讓 UI 變得割裂,而且就算不是透明色,圓角也會暴露出不友善的場景:

所以官方認為需要重構 Flutter 的鍵盤區域邏輯來適應這個問題,主要也是因為以前一直把鍵盤下方當成不可見區域,所以設計上一直沒有管理這一塊,所以這次官方定下重構的邏輯是:

layout 還是要避讓鍵盤,但 paint 不能停在鍵盤上方,背景繪製必須延伸到鍵盤背後。

說人話就是:內容不要真的排版到鍵盤下面,但背景色要畫到鍵盤下面

同時官方也歸類出目前的鍵盤問題主要有三類:

1、Flutter framework 自己的問題

類似 Scaffold.bottomSheetshowBottomSheetScaffoldState.showBottomSheet,它們本來就和 Scaffold 深度綁定,而且視覺上應該從 sheet 一直延續到鍵盤區域。

但 iOS 26 下如果 sheet 前景色和 Scaffold 背景色不一樣,鍵盤半透明後就會看到 Scaffold 背景色透出來,導致 sheet 和鍵盤區域斷開,也就是前面我們看到的問題。

所以這個問題應該 Flutter framework 自己處理,因為 Scaffold 本身就知道 persistent sheet 的存在,也能拿到 sheet 的背景色。

2、Scaffold body descendants / Overlay

這類問題更麻煩,因為一般發生在業務自訂元件或第三方套件,類似:

  • 自訂 dialog/sheet/modal 作為 Scaffold.body 的 descendant
  • 元件透過 OverlayEntry 顯示,但不 push route
  • barrier、scrim、背景層只畫到鍵盤上緣
  • 鍵盤區域背後露出 Scaffold 背景或其他底層顏色

比如這個灰色 barrier 只覆蓋到鍵盤頂部,鍵盤後面沒有繼續畫灰色,所以 iOS 26 半透明鍵盤會透出不一致的底色:

上圖問題主要來自 wolt_modal_sheet,它 push 了自己的 route,但 route 內部又有一個帶 persistent bottom sheet 的 Scaffold,而且 body 前景色和 Scaffold 背景色不同,所以還是會出現視覺斷層。

實際上這類問題主要是第三方套件自己實作的問題,比如:

  • 需要全螢幕 barrier 的 dialog/modal/sheet,盡量 push route,而不是直接塞進被鍵盤壓縮後的 Scaffold body
  • overlay-based 元件要明確處理 viewInsets.bottom
  • 需要在鍵盤區域補背景

3、Modal padding 寫法問題

最後一個問題也是最容易遇到的,很多人寫 showModalBottomSheet 時,為了讓內容避開鍵盤,會手動加 .bottom

showModalBottomSheet<void>(
  context: context,
  builder: (context) => Padding(
    padding: EdgeInsets.only(
      bottom: MediaQuery.viewInsetsOf(context).bottom,
    ),
    child: ColoredBox(
      color: Colors.red,
      child: ...
    ),
  ),
);

問題在於,如果你把背景色放在這個 Padding 裡面,顏色就只會覆蓋內容區域,不會覆蓋鍵盤區域:

這個結構的問題是Padding 在外,而顏色在內,內容被頂上去了,但顏色也跟著停在鍵盤上方

所以正確寫法應該反過來,顏色在外,padding 在內,內容仍然避讓鍵盤,但背景色會延伸到鍵盤背後:

showModalBottomSheet<void>(
  context: context,
  builder: (context) => ColoredBox(
    color: Colors.red,
    child: Padding(
      padding: EdgeInsets.only(
        bottom: MediaQuery.viewInsetsOf(context).bottom,
      ),
      child: ...
    ),
  ),
);

這裡官方還收集了一批有問題的第三方生態套件,包括:

  • modal_bottom_sheet
  • wolt_modal_sheet
  • smooth_sheets
  • sliding_up_panel
  • snapping_sheet
  • flutter_smart_dialog
  • motion_toast
  • delightful_toast
  • another_flushbar
  • keyboard_actions
  • shadcn_ui
  • fluent_ui
  • for_ui

很多套件都屬於第二種情況,因為過去大多預設軟鍵盤會繼續是不透明、非圓角的,所以大家基本上都沒有認真處理「鍵盤背後的背景應該由誰來畫」這個問題。

所以實際上的結果就是,官方要修 Flutter framework 這類第一種場景的情況,然後指導社群適配第二、第三種寫法。

針對 Flutter framework,官方初步是計畫在 Scaffold 層增加一個新的繪製 slot,用來在鍵盤區域畫 backdrop,這個 backdrop 的行為大概是:

  • 只在鍵盤彈出時啟用
  • 只在存在 persistent bottom sheet 時啟用
  • 只在 resizeToAvoidBottomInset == true 時啟用
  • 只在 sheet 有非透明背景色時啟用
  • backdrop 顏色來自目前 sheet 或 dismissing sheet 的 effective background color
  • backdrop 放在 foreground widget 下方
  • IgnorePointer 包住,不改變點擊命中行為

也就是 Scaffold 仍然把 body 縮到鍵盤上方,但額外把 sheet 的背景色畫到鍵盤後面,這樣做的好處是相容性強,對 Android 和 iOS 26 之前的平台幾乎沒有可見影響,因為舊鍵盤本來就會擋住這塊區域。

當然,這種設計還是做不到原生 iOS 更理想的效果,如下圖是原生 iOS 的一個效果,列表是會經過模糊的半透明鍵盤:

因為 resizeToAvoidBottomInset 存在根本性缺陷,它實際上是為了調整 Flutter 主體大小來保持在鍵盤上方的行為而加入的,理想的鍵盤解決方案不是將 Scaffold body 的大小限制在鍵盤上方的區域,而是讓 Scaffold body 和所有 Scroll 視圖內部使用 viewInsets.bottom 來填充對應內容,這樣可捲動的內容在鍵盤下方也可以看到。

但是現在問題就出在:

  • 如果 Scaffold body 自己加了 viewInsets.bottom padding,裡面的 ListView / GridView / CustomScrollView 也加 padding,就會變成兩倍鍵盤高度。
  • 如果 Scaffold 判斷 child 是不是 scroll view,然後決定自己要不要 padding,又違反 Flutter 的設計原則:父 widget 不應該窺探子 widget 的內部型別。
  • 如果完全讓 scroll view 自己處理 padding,那麼非捲動頁面裡的 TextField 又可能被鍵盤永久擋住。

所以目前官方最終選擇了第三條路:

Scaffold 仍然負責鍵盤避讓,把鍵盤區域當成不可排版區域,但相關元件要把背景繪製延伸進去,保證視覺連續。

所以這個問題核心不只是 Flutter Framework 要修復,你用的套件和程式碼也需要跟著調整,而且這個實作看起來對漸層背景、圖片背景、blur / glass 效果的支援也不一定友善,所以只能算是一個折衷結果:

連結

flutter.dev/go/ios-26-k…


原文出處:https://juejin.cn/post/7651505036707512383


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝16   💬1   ❤️1
460
🥈
我愛JS
📝1   ❤️1
21
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登