Flutter性能揭秘之RepaintBoundary
點擊上方藍字關注我,知識會給你力量
Flutter會在屏幕上繪制Widget。如果一個Widget的內容需要更新,那就只能重繪了。盡管如此,F(xiàn)lutter同樣會重新繪制一些Widget,而這些Widget的內容仍有部分未被改變。這可能會影響應用程序的執(zhí)行性能,有時影響會非常巨大。如果您正在尋找一種方法,來防止不必要的部分重繪,您可以考慮利用RepaintBoundary。
在這篇博客理,我們將探討Flutter中的RepaintBoundary。我們將看到如何實現(xiàn)RepaintBoundary的演示程序以及如何在您的flutter應用程序中使用它。
RepaintBoundary
RepaintBoundary類是Null安全的。首先,你需要了解什么是Flutter中的RepaintBoundary。它是一個為它的Child設置不同的展示層級的Widget。這個Widget為它的Child設置了一個不同的展示層級,如果一個子樹與它周圍的部分相比,會在意想不到的短時間內重新繪制,F(xiàn)lutter建議你使用RepaintBoundary來進一步提高性能。
為什么需要使用RepaintBoundary呢。
Flutter Widget與RenderObjects有關。一個RenderObject有一個叫做paint的函數(shù),它被用來執(zhí)行繪畫過程。盡管如此,無論相關組件的內容是否發(fā)生變化,都可以使用繪制方法。這是因為,如果其中一個RenderObjects被設定為dirty,F(xiàn)lutter可能會對類似Layer中的其他RenderObjects進行重新繪制。當一個RenderObject需要利用RenderObject.markNeedsPaint進行重繪的時候,它就會建議它最接近的前輩進行重繪。祖先也會對它的前輩做同樣的事情,直到根RenderObject。當一個RenderObject的paint策略被啟動時,它在類似層中的所有相關RenderObjects都將被重新paint。
而有時,當一個RenderObject應該被重繪時,類似層中的其他RenderObjects不應該被重繪,因為它們的繪制產(chǎn)物保持不變。因此,如果我們只是對某些RenderObjects進行重繪,那會更好。利用RepaintBoundary可以幫助我們在渲染樹上限制markNeedsPaint的生成,在渲染樹下限制paintChild的生成。
RepaintBoundary可以將先前的渲染對象與相關的渲染對象解耦。通過這種方式,只對內容發(fā)生變化的子樹進行重繪是可行的。利用RepaintBoundary可以進一步提高應用程序的執(zhí)行效率,特別是當不應該被重繪的子樹需要大量的工作來重繪時。
我們將做一個簡單的演示程序,背景是利用CustomPainter繪制的,有10000個橢圓。同時還有一個光標,在客戶接觸到屏幕的最后一個位置后移動。下面是沒有RepaintBoundary的代碼。
示例
在正文中,我們將創(chuàng)建一個Stack widget。在里面,我們將添加一個StackFit.expand,并添加兩個部件:_buildBackground(),和_buildCursor()。我們將定義以下代碼。
Stack(
fit: StackFit.expand,
children: <Widget>[
_buildBackground(),
_buildCursor(),
],
),
_buildBackground() widget
在_buildBackground()小組件中。我們將返回CustomPaint() widget。在里面,我們將在繪畫器上添加BackgroundColor類。我們將在下面定義。另外,我們將添加isComplex參數(shù)為true,這意味著是否提示這個圖層的繪畫應該被緩存,willChange是false意味著是否應該告訴光柵緩存,這個繪畫在下一幀可能會改變。
Widget _buildBackground() {
return CustomPaint(
painter: BackgroundColor(MediaQuery.of(context).size),
isComplex: true,
willChange: false,
);
}
BackgroundColor class
我們將創(chuàng)建一個BackgroundColor來擴展CustomPainter。
import 'dart:math';
import 'package:flutter/material.dart';
class BackgroundColor extends CustomPainter {
static const List<Color> colors = [
Colors.orange,
Colors.purple,
Colors.blue,
Colors.green,
Colors.purple,
Colors.red,
];
Size _size;
BackgroundColor(this._size);
@override
void paint(Canvas canvas, Size size) {
final Random rand = Random(12345);
for (int i = 0; i < 10000; i++) {
canvas.drawOval(
Rect.fromCenter(
center: Offset(
rand.nextDouble() * _size.width - 100,
rand.nextDouble() * _size.height,
),
width: rand.nextDouble() * rand.nextInt(150) + 200,
height: rand.nextDouble() * rand.nextInt(150) + 200,
),
Paint()
..color = colors[rand.nextInt(colors.length)].withOpacity(0.3)
);
}
}
@override
bool shouldRepaint(BackgroundColor other) => false;
}
_buildCursor() widget
在這個Widget,我們將返回Listener Widget。我們將在onPointerDown/Move方法中添加_updateOffset()組件,并添加CustomPaint。在里面,我們將添加一個Key和CursorPointer類。我們將在下面定義。另外,我們將添加ConstrainedBox()。
Widget _buildCursor() {
return Listener(
onPointerDown: _updateOffset,
onPointerMove: _updateOffset,
child: CustomPaint(
key: _paintKey,
painter: CursorPointer(_offset),
child: ConstrainedBox(
constraints: BoxConstraints.expand(),
),
),
);
}
CursorPointer class
我們將創(chuàng)建一個CursorPointer來擴展CustomPainter。
import 'package:flutter/material.dart';
class CursorPointer extends CustomPainter {
final Offset _offset;
CursorPointer(this._offset);
@override
void paint(Canvas canvas, Size size) {
canvas.drawCircle(
_offset,
10.0,
new Paint()..color = Colors.green,
);
}
@override
bool shouldRepaint(CursorPointer old) => old._offset != _offset;
}
當我們運行應用程序時,我們應該得到下面屏幕的輸出,如屏幕下的視頻。如果你試圖在屏幕上移動指針,應用程序將非常滯后,因為它重新繪制背景,需要昂貴的計算。
下面,我們將添加RepaintBoundary。解決上述問題的答案是將CustomPaint部件包裝成RepaintBoundary的子Widget。
Widget _buildBackground() {
return RepaintBoundary(
child: CustomPaint(
painter: BackgroundColor(MediaQuery.of(context).size),
isComplex: true,
willChange: false,
),
);
}
當我們運行應用程序時,我們應該得到屏幕的輸出,就像屏幕下面的視頻一樣。有了這個簡單的改變,現(xiàn)在當Flutter重繪光標時,背景就不需要重繪了。應用程序應該不再是滯后的了。
整個代碼如下所示。
import 'package:flutter/material.dart';
import 'package:flutter_repaint_boundary_demo/background_color.dart';
import 'package:flutter_repaint_boundary_demo/cursor_pointer.dart';
class HomePage extends StatefulWidget {
@override
State createState() => new _HomePageState();
}
class _HomePageState extends State<HomePage> {
final GlobalKey _paintKey = new GlobalKey();
Offset _offset = Offset.zero;
Widget _buildBackground() {
return RepaintBoundary(
child: CustomPaint(
painter: BackgroundColor(MediaQuery.of(context).size),
isComplex: true,
willChange: false,
),
);
}
Widget _buildCursor() {
return Listener(
onPointerDown: _updateOffset,
onPointerMove: _updateOffset,
child: CustomPaint(
key: _paintKey,
painter: CursorPointer(_offset),
child: ConstrainedBox(
constraints: BoxConstraints.expand(),
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.cyan,
title: const Text('Flutter RepaintBoundary Demo'),
),
body: Stack(
fit: StackFit.expand,
children: <Widget>[
_buildBackground(),
_buildCursor(),
],
),
);
}
_updateOffset(PointerEvent event) {
RenderBox? referenceBox = _paintKey.currentContext?.findRenderObject() as RenderBox;
Offset offset = referenceBox.globalToLocal(event.position);
setState(() {
_offset = offset;
});
}
}
總結
在文章中,我解釋了Flutter中RepaintBoundary的基本結構;你可以根據(jù)你的選擇來修改這個代碼。這是我對RepaintBoundary On User Interaction的一個小的介紹,它在使用Flutter時是可行的。
翻譯自 https://medium.flutterdevs.com/repaintboundary-in-flutter-9e2f426ff579
向大家推薦下我的網(wǎng)站 https://www.yuque.com/xuyisheng 點擊原文一鍵直達
專注 Android-Kotlin-Flutter 歡迎大家訪問
往期推薦
更文不易,點個“三連”支持一下??
