当前位置:网站首页>Talk about repaintboundary in fluent
Talk about repaintboundary in fluent
2022-07-23 07:31:00 【A floating boat】
A lazy afternoon , I saw this article by chance Flutter Record on pit , The author's question aroused my curiosity . The author's questions are described below :
A chat page , Because the dialog shape needs to be customized , So... Was adopted CustomPainter From define drawing dialog . It was found during the test that ipad mini Scrolling up and down the list of dialog boxes appears crash, Further tests have found that chatting also occurs frequently crash.
In expressing sympathy for the author's experience , It also reminds me of using CustomPainter The place of .
Looking for problems
stay flutter_deer There is such a page in :

The outermost layer of the page is a SingleChildScrollView, The circle above is a custom CustomPainter, Below is a ListView list .
It's not complicated to implement this ring graph . Inherit CustomPainter, rewrite paint And shouldRepaint The method can .paint Method is responsible for drawing specific figures ,shouldRepaint The method is responsible for telling Flutter Whether to redraw when refreshing the layout . The general strategy is to shouldRepaint In the method , We determine whether we need to redraw by comparing the data before and after .
When I slide the page , Found... In custom ring chart paint Methods are constantly being implemented .???shouldRepaint The method failed ? In fact, the annotation document is very clear , I just blame myself for not reading it carefully .( The source code of this article is based on Flutter SDK edition v1.12.13+hotfix.3)
/// If the method returns false, then the [paint] call might be optimized
/// away.
///
/// It's possible that the [paint] method will get called even if
/// [shouldRepaint] returns false (e.g. if an ancestor or descendant needed to
/// be repainted). It's also possible that the [paint] method will get called
/// without [shouldRepaint] being called at all (e.g. if the box changes
/// size).
///
/// If a custom delegate has a particularly expensive paint function such that
/// repaints should be avoided as much as possible, a [RepaintBoundary] or
/// [RenderRepaintBoundary] (or other render object with
/// [RenderObject.isRepaintBoundary] set to true) might be helpful.
///
/// The `oldDelegate` argument will never be null.
bool shouldRepaint(covariant CustomPainter oldDelegate);
Two points are mentioned in the note :
Even if shouldRepaint return false, It is also possible to call paint Method ( for example : If the size of the component changes ).
If you customize View More complicated , Redrawing should be avoided as much as possible . Use RepaintBoundary perhaps RenderObject.isRepaintBoundary by true It may help you .
Obviously the first problem I have is . Look over SingleChildScrollView Source code we found a problem :
@override
void paint(PaintingContext context, Offset offset) {
if (child != null) {
final Offset paintOffset = _paintOffset;
void paintContents(PaintingContext context, Offset offset) {
context.paintChild(child, offset + paintOffset); <----
}
if (_shouldClipAtPaintOffset(paintOffset)) {
context.pushClipRect(needsCompositing, offset, Offset.zero & size, paintContents);
} else {
paintContents(context, offset);
}
}
}
stay SingleChildScrollView It's necessary to draw its child, That is to say, the final implementation is to paintChild Method .
void paintChild(RenderObject child, Offset offset) {
if (child.isRepaintBoundary) {
stopRecordingIfNeeded();
_compositeChild(child, offset);
} else {
child._paintWithContext(this, offset);
}
}
void _paintWithContext(PaintingContext context, Offset offset) {
...
_needsPaint = false;
try {
paint(context, offset); //<-----
} catch (e, stack) {
_debugReportException('paint', e, stack);
}
}
stay paintChild In the method , as long as child.isRepaintBoundary by false, Then it will carry out paint Method , I'll just skip over here shouldRepaint.
solve the problem
isRepaintBoundary Mentioned in the note above , in other words isRepaintBoundary by true when , We can synthesize views directly , Avoid redrawing .Flutter For us RepaintBoundary, It's the encapsulation of this operation , It's convenient for us to use .
class RepaintBoundary extends SingleChildRenderObjectWidget {
const RepaintBoundary({ Key key, Widget child }) : super(key: key, child: child);
@override
RenderRepaintBoundary createRenderObject(BuildContext context) => RenderRepaintBoundary();
}
class RenderRepaintBoundary extends RenderProxyBox {
RenderRepaintBoundary({ RenderBox child }) : super(child);
@override
bool get isRepaintBoundary => true; /// <-----
}
So the solution to the problem is very simple : stay CustomPaint An outer covering RepaintBoundary. Click here for detailed source code .
Performance comparison
In fact, I didn't find this problem before , Because the whole page slides smoothly .
In order to compare the performance before and after clear comparison , I repeatedly add ten of these circles on this page to slide test . The picture below is timeline Result :


Before the optimization, the sliding will be obviously not smooth , The actual drawing of each frame needs to be close to 16ms, After optimization, only 1ms. In this scenario example , Not a lot of drawing ,GPU No pressure at all . If it's just a previous ring chart , In fact, this optimization is not necessary , It's just better , Avoid unnecessary drawing .
When looking for relevant information , I am here stackoverflow We found An interesting example .
The author drew... On the screen 5000 Color circles to form a similar “ Kaleidoscope ” Background of the effect .
class ExpensivePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
print("Doing expensive paint job");
Random rand = new Random(12345);
List<Color> colors = [
Colors.red,
Colors.blue,
Colors.yellow,
Colors.green,
Colors.white,
];
for (int i = 0; i < 5000; i++) {
canvas.drawCircle(
new Offset(
rand.nextDouble() * size.width, rand.nextDouble() * size.height),
10 + rand.nextDouble() * 20,
new Paint()
..color = colors[rand.nextInt(colors.length)].withOpacity(0.2));
}
}
@override
bool shouldRepaint(ExpensivePainter other) => false;
}
At the same time, a small black dot on the screen will follow the fingers . But each slide will cause the background to be redrawn . The optimization method is the same as above , I tested this Demo, We get the following result .

In this scenario example , draw 5000 A circle for GPU It brings a lot of pressure , With RepaintBoundary Use , The effect of optimization is obvious .
To find out
that RepaintBoundary What is it ?RepaintBoundary Just redraw the border , Used to redraw independently of the parent layout .
stay Flutter SDK Part of it Widget Did this treatment , such as TextField、SingleChildScrollView、AndroidView、UiKitView etc. . Most commonly used ListView stay item Also used by default on RepaintBoundary:
You can think about why these components use RepaintBoundary.

Then in the source code above child.isRepaintBoundary by true The place of , We see that we call _compositeChild Method ;
void _compositeChild(RenderObject child, Offset offset) {
...
// Create a layer for our child, and paint the child into it.
if (child._needsPaint) {
repaintCompositedChild(child, debugAlsoPaintedParent: true); // <---- 1
}
final OffsetLayer childOffsetLayer = child._layer;
childOffsetLayer.offset = offset;
appendLayer(child._layer);
}
static void repaintCompositedChild(RenderObject child, { bool debugAlsoPaintedParent = false }) {
_repaintCompositedChild( // <---- 2
child,
debugAlsoPaintedParent: debugAlsoPaintedParent,
);
}
static void _repaintCompositedChild(
RenderObject child, {
bool debugAlsoPaintedParent = false,
PaintingContext childContext,
}) {
...
OffsetLayer childLayer = child._layer;
if (childLayer == null) {
child._layer = childLayer = OffsetLayer(); // <---- 3
} else {
childLayer.removeAllChildren();
}
childContext ??= PaintingContext(child._layer, child.paintBounds);
/// The creation is complete , Drawing
child._paintWithContext(childContext, Offset.zero);
childContext.stopRecordingIfNeeded();
}
child._needsPaint by true Will finally pass _repaintCompositedChild The method is at present child Create a layer (layer).
The layers mentioned here are still very abstract , How to observe it directly ? We can program in main method debugRepaintRainbowEnabled Variable set to true. It can help us visualize the redrawing of the rendering tree in the application . The principle is actually to carry out the above stopRecordingIfNeeded When the method is used , An extra color rectangle is drawn :
@protected
@mustCallSuper
void stopRecordingIfNeeded() {
if (!_isRecording)
return;
assert(() {
if (debugRepaintRainbowEnabled) { // <-----
final Paint paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 6.0
..color = debugCurrentRepaintColor.toColor();
canvas.drawRect(estimatedBounds.deflate(3.0), paint);
}
return true;
}());
}
The effect is as follows :

Different colors represent different layers . When redrawing occurs , The corresponding rectangle will also change color .
Before redrawing , need markNeedsPaint Method to mark redrawn nodes .
void markNeedsPaint() {
if (_needsPaint)
return;
_needsPaint = true;
if (isRepaintBoundary) {
// If we always have our own layer, then we can just repaint
// ourselves without involving any other nodes.
assert(_layer is OffsetLayer);
if (owner != null) {
owner._nodesNeedingPaint.add(this);
owner.requestVisualUpdate(); // Update drawing
}
} else if (parent is RenderObject) {
final RenderObject parent = this.parent;
parent.markNeedsPaint();
assert(parent == this.parent);
} else {
if (owner != null)
owner.requestVisualUpdate();
}
}
markNeedsPaint In the method, if isRepaintBoundary by false, Will call the parent node's markNeedsPaint Method , until isRepaintBoundary by true when , Just put the current RenderObject Added to the _nodesNeedingPaint in .
When drawing each frame , call flushPaint Method update view .
void flushPaint() {
try {
final List<RenderObject> dirtyNodes = _nodesNeedingPaint; <-- Get the dirty nodes that need to be drawn
_nodesNeedingPaint = <RenderObject>[];
// Sort the dirty nodes in reverse order (deepest first).
for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
assert(node._layer != null);
if (node._needsPaint && node.owner == this) {
if (node._layer.attached) {
PaintingContext.repaintCompositedChild(node); <--- Redraw here , Depth first
} else {
node._skippedPaintingOnLayer();
}
}
}
} finally {
if (!kReleaseMode) {
Timeline.finishSync();
}
}
}
In this way, local redrawing is realized , Separate the redrawing of the child from the parent .
tips: Here's a little bit of attention , Usually we click the water ripple effect of the button to redraw the layer closest to its parent . We need to deal with it according to the specific situation of the page . This is in the official program flutter_gallery There is a similar treatment .
summary
In fact, it's just a sentence , Use it reasonably according to the scene RepaintBoundary, It can help you improve your performance . In fact, the optimization direction is more than RepaintBoundary, also RelayoutBoundary. Then I won't introduce , You can check the links at the end of the article if you are interested .
If this article enlightens and helps you , Support me a lot ! Finally, I hope you will support my Flutter Open source project flutter_deer, I will talk about Flutter We put all our practice in it .
Reference resources
If you are interested, you can see this boss about RepaintBoundary A column for :
Flutter Drawing exploration 5 | In depth analysis of redrawing range RepaintBoundary | Clock in on the seventh
Of course, you can also see the articles written by the boss on painting :
dFlutter Draw exploration column

边栏推荐
- 小程序毕设作品之微信校园二手书交易小程序毕业设计成品(7)中期检查报告
- 深度学习模型的版权保护研究综述
- Unity notes - use of addressables
- Flutter内存泄漏检测
- 小程序wx.setStorageSync后,在用getStorageSync获取数据有时会获取不到
- VR panoramic zoo, a zoo business card with different achievements
- 小程序毕设作品之微信酒店预订小程序毕业设计(8)毕业设计论文模板
- 【技术科普】联盟链Layer2-论一种新的可能性
- Class class added in ES6
- 编写一个具有搜索提示的搜索框
猜你喜欢

Digital collections start the 100 billion level market

Zhimeng dedecms forgot to manage the background password retrieval method

常用机械设备安全虚拟仿真系统的应用场景及方案

初识Flutter中的Layer

C语言 程序环境

深度学习模型的版权保护研究综述

妙用cURL

Are most programmers eliminated after the age of 45? The truth chilled everyone's heart

编写一个具有搜索提示的搜索框

js确定滚动元素和解决tab切换滚动位置独立
随机推荐
Overview of the development of pseudo defense in cyberspace: from pseudo concept to "pseudo +" ecology
Vector3.Lerp
session、cookie、token 详解
Go语言系列-协程-GMP简介-附字节跳动内推
上采样方式(反卷积、插值、反池化)
开源2周年,openGauss Developer Day 2022全程亮点回顾!
MySql的DDL和DML和DQL的基本语法
【无标题】
[FAQ] common reasons and solutions for the failure of in app payment services to pull up the payment page
支持多数不规则用户的隐私保护联邦学习框架
局域网SDN技术硬核内幕 - 前传 CPU里面有什么?
Datagrip tutorial (GIF version)
导出功能单独调用
基于API调用管理的SDN应用层DDoS攻击防御机制
A survey of copyright protection research on deep learning model
ES6解决异步问题
redis常用基础配置文件
【学习笔记之菜Dog学C】大厂笔试,就这?
小程序毕设作品之微信校园二手书交易小程序毕业设计成品(8)毕业设计论文模板
[SSM]统一结果封装