桌面端預設的 PrimaryScrollController
`PrimaryScrollController` 將不再自動附加到桌面端的垂直 `ScrollView`。
摘要
#PrimaryScrollController API 已更新,現在不會再自動附加到桌面平台上的垂直 ScrollView。
背景說明
#
在此變更之前,如果 ScrollView 的滾動方向為 Axis.vertical,且尚未提供 ScrollController,則
ScrollView.primary 會預設為 true。這讓常見的 UI 模式(例如 iOS 上的捲動至頂功能)在 Flutter 應用程式中能夠開箱即用。然而,在桌面端,這個預設值經常導致以下的 assertion 錯誤:
ScrollController attached to multiple ScrollViews.
雖然行動應用程式通常一次只顯示一個 ScrollView,
但桌面 UI 模式則更常同時並排顯示多個 ScrollView。
先前的 PrimaryScrollController 實作方式與這種模式產生衝突,
導致經常出現無法提供幫助的錯誤訊息。
為了解決這個問題,PrimaryScrollController 已經新增了額外的參數,
同時針對多個依賴它的元件 (Widget) 提供了更完善的錯誤訊息。
變更說明
#
先前的 ScrollView 實作方式,
會讓所有沒有 ScrollController 的垂直 ScrollView,在所有平台上,
primary 預設為 true。
這個預設行為並不總是很明確,
特別是因為它與 PrimaryScrollController 本身是分開的。
// Previously, this ListView would always result in primary being true,
// and attached to the PrimaryScrollController on all platforms.
Scaffold(
body: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Text('Item $index');
}
),
);
此實作將 ScrollView.primary 改為可為 null,並將預設決策邏輯移至 PrimaryScrollController。
當 primary 為 null 且未提供 ScrollController 時,ScrollView
會查找 PrimaryScrollController,並改為呼叫 shouldInherit 來判斷
指定的 ScrollView 是否應該使用 PrimaryScrollController。
PrimaryScrollController 類別的新成員,
automaticallyInheritForPlatforms 和 scrollDirection,會在
shouldInherit 中被評估,讓使用者能更清楚且可控地掌握
PrimaryScrollController 的行為。
預設情況下,行動裝置平台會維持向下相容性。
PrimaryScrollController.shouldInherit 對於垂直的
ScrollView 會回傳 true。在桌面端,預設則回傳 false。
// Only on mobile platforms will this attach to the PrimaryScrollController by
// default.
Scaffold(
body: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Text('Item $index');
}
),
);
若要變更預設行為,使用者可以將 ScrollView.primary 設為 true 或 false,以明確管理個別 ScrollView 的 PrimaryScrollController。
針對多個 ScrollView 之間的行為,現在可以透過設定特定平台,以及偏好繼承的滾動方向,來設定 PrimaryScrollController。
使用 PrimaryScrollController 的元件 (Widget),例如 NestedScrollView、Scrollbar
和 DropdownMenuButton,其現有功能將不受影響。像 iOS 的 scroll-to-top 這類功能也會如預期般運作,無需進行任何遷移。
ScrollAction 以及桌面上的 ScrollIntent 是唯一受此變更影響且需要遷移的類別。預設情況下,若目前的 Focus
包含於 Scrollable 中,則會使用 PrimaryScrollController 來執行備援的鍵盤滾動 Shortcuts。由於在桌面平台上並排顯示多個
ScrollView 十分常見,因此 Flutter 無法自行決定「此畫面中哪一個 ScrollView 應該是主要的,並接收鍵盤滾動操作?」
若在此變更前存在多個 ScrollView,也會拋出相同的斷言(ScrollController attached to multiple ScrollViews.)。
現在,在桌面平台上,使用者需要指定 primary: true,以指定哪一個 ScrollView 作為備援,接收未處理的鍵盤
Shortcuts。
遷移指南
#遷移前的程式碼:
// These side-by-side ListViews would throw errors from Scrollbars and
// ScrollActions previously due to the PrimaryScrollController.
Scaffold(
body: LayoutBuilder(
builder: (context, constraints) {
return Row(
children: [
SizedBox(
height: constraints.maxHeight,
width: constraints.maxWidth / 2,
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Text('List 1 - Item $index');
}
),
),
SizedBox(
height: constraints.maxHeight,
width: constraints.maxWidth / 2,
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Text('List 2 - Item $index');
}
),
),
]
);
},
),
);
遷移後的程式碼:
// These side-by-side ListViews will no longer throw errors, but for
// default ScrollActions, one will need to be designated as primary.
Scaffold(
body: LayoutBuilder(
builder: (context, constraints) {
return Row(
children: [
SizedBox(
height: constraints.maxHeight,
width: constraints.maxWidth / 2,
child: ListView.builder(
// This ScrollView will use the PrimaryScrollController
primary: true,
itemBuilder: (BuildContext context, int index) {
return Text('List 1 - Item $index');
}
),
),
SizedBox(
height: constraints.maxHeight,
width: constraints.maxWidth / 2,
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Text('List 2 - Item $index');
}
),
),
]
);
},
),
);
時程
#
合併於版本:3.3.0-0.0.pre
於穩定版發佈:3.3
參考資料
#API 文件:
設計文件:
相關議題:
相關 PR:
Unless stated otherwise, the documentation on this site reflects Flutter 3.44.0. Page last updated on 2026-06-14. View source or report an issue.