剪輯行為
概述
#Flutter 現在預設不進行剪輯,除了少數特殊的小部件(如 ClipRect)。要覆蓋不剪輯的預設行為,請在構建小部件時顯式設定 clipBehavior。
背景
#以前,Flutter 因為剪輯而變慢。例如,在 2018 年 5 月,Flutter Gallery 應用的基準測試平均幀光柵化時間約為 35 毫秒,而實現流暢 60fps 渲染的預算是 16 毫秒。透過移除不必要的剪輯及其相關操作,我們將每幀時間從 35 毫秒縮短到 17.5 毫秒,速度提升了近一倍。
當時剪輯帶來的最大開銷是,Flutter 會在每次剪輯後(除非是簡單的軸對齊矩形剪輯)呼叫 saveLayer,以避免出現 Issue 18057 中描述的邊緣溢位偽影。這種行為透過 Card、Chip、Button 等小部件在 Material 應用中普遍存在,導致 PhysicalShape 和 PhysicalModel 對其內容進行剪輯。
對於舊裝置來說,呼叫 saveLayer 尤其昂貴,因為它會建立一個離屏渲染目標,而渲染目標切換有時會花費大約 1 毫秒。
即使沒有 saveLayer 呼叫,剪輯仍然昂貴,因為它會應用於所有後續繪製,直到恢復為止。因此,單個剪輯可能會減慢數百次繪製操作的效能。
除了效能問題,Flutter 還存在一些正確性問題,因為剪輯沒有在一個地方進行管理和實現。在幾個地方,saveLayer 被錯誤地插入,因此它只會增加效能成本,而無法修復任何邊緣溢位偽影。
因此,我們在這次重大更改中統一了 clipBehavior 的控制及其實現。為了提高效能,大多數小部件的預設 clipBehavior 為 Clip.none,除了以下小部件:
ClipPath預設為Clip.antiAliasClipRRect預設為Clip.antiAliasClipRect預設為Clip.hardEdgeStack預設為Clip.hardEdgeEditableText預設為Clip.hardEdgeListWheelScrollView預設為Clip.hardEdgeSingleChildScrollView預設為Clip.hardEdgeNestedScrollView預設為Clip.hardEdgeShrinkWrappingViewport預設為Clip.hardEdge
遷移指南
#您的程式碼有 4 種遷移方式可供選擇:
- 如果您的內容不需要剪輯(例如,小部件的子項沒有超出其父邊界),請將程式碼原樣保留。這很可能會對您應用的整體效能產生積極影響。
- 如果您需要剪輯,並且沒有抗鋸齒的剪輯對您(和您的客戶)的眼睛來說已經足夠,請新增
clipBehavior: Clip.hardEdge。這是剪輯矩形或具有非常小曲線區域(如圓角矩形的角)的形狀時的常見情況。 - 如果您需要抗鋸齒剪輯,請新增
clipBehavior: Clip.antiAlias。這會以略高的成本提供更平滑的邊緣。這是處理圓形和弧形時的常見情況。 - 如果您想獲得與之前(2018 年 5 月)完全相同的行為,請新增
clip.antiAliasWithSaveLayer。請注意,這會帶來很高的效能成本。這可能很少需要。您可能需要這種行為的一個例子是,當您有一個影像疊加在非常不同的背景顏色上時。在這些情況下,請考慮是否可以在一個點避免多種顏色重疊(例如,僅在影像不存在的地方顯示背景顏色)。
對於 Stack 小部件,如果您之前使用了 overflow: Overflow.visible,請將其替換為 clipBehavior: Clip.none。
對於 ListWheelViewport 小部件,如果您之前指定了 clipToSize,請將其替換為相應的 clipBehavior:對於 clipToSize = false,使用 Clip.none;對於 clipToSize = true,使用 Clip.hardEdge。
遷移前的程式碼
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: Stack(
overflow: Overflow.visible,
children: const <Widget>[
SizedBox(
width: 100,
height: 100,
),
],
),
),
),
);遷移後的程式碼
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: Stack(
clipBehavior: Clip.none,
children: const <Widget>[
SizedBox(
width: 100.0,
height: 100.0,
),
],
),
),
),
);時間線
#首次引入版本:各種
穩定版本:2.0.0
參考資料
#API 文件
相關問題
相關 PR
- PR 5420:移除不必要的 saveLayer
- PR 18576:向 Material 和相關小部件新增 Clip 列舉
- PR 18616:從 dart 中移除剪輯後的 saveLayer
- PR 5647:向 ClipPath/ClipRRect 和 PhysicalShape 層新增 ClipMode
- PR 5670:向 canvas 剪輯呼叫新增抗鋸齒開關
- PR 5853:將剪輯模式重新命名為剪輯行為
- PR 5868:在 compositing.dart 中將 clip 重新命名為 clipBehavior
- PR 5973:當存在剪輯時,呼叫 drawPaint 而不是 drawPath
- PR 5952:如果可能,在沒有剪輯的情況下呼叫 drawPath
- PR 20205:將預設 clipBehavior 設定為 Clip.none 並更新測試
- PR 20538:向更多 Material 按鈕公開 clipBehavior
- PR 20751:向 InkWell 新增 customBorder 以便它可以剪輯 ShapeBorder
- PR 20752:再次將預設剪輯設定為 Clip.none
- PR 21012:向更多按鈕新增預設無剪輯測試
- PR 21703:將 ClipRect 的預設 clipBehavior 設定為 hardEdge
- PR 21826:ClipRectLayer 缺失預設 hardEdge 剪輯