Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
d54cdf9e
Unverified
Commit
d54cdf9e
authored
May 17, 2022
by
Dan Field
Committed by
GitHub
May 17, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add a mechanism to observe layer tree composition. (#103378)
parent
c62a7d41
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
442 additions
and
4 deletions
+442
-4
layer.dart
packages/flutter/lib/src/rendering/layer.dart
+144
-3
object.dart
packages/flutter/lib/src/rendering/object.dart
+16
-1
layers_test.dart
packages/flutter/test/rendering/layers_test.dart
+249
-0
object_test.dart
packages/flutter/test/rendering/object_test.dart
+33
-0
No files found.
packages/flutter/lib/src/rendering/layer.dart
View file @
d54cdf9e
...
...
@@ -135,6 +135,91 @@ class AnnotationResult<T> {
/// * [RenderView.compositeFrame], which implements this recomposition protocol
/// for painting [RenderObject] trees on the display.
abstract
class
Layer
extends
AbstractNode
with
DiagnosticableTreeMixin
{
final
Map
<
int
,
VoidCallback
>
_callbacks
=
<
int
,
VoidCallback
>{};
static
int
_nextCallbackId
=
0
;
/// Whether the subtree rooted at this layer has any composition callback
/// observers.
///
/// This only evaluates to true if the subtree rooted at this node has
/// observers. For example, it may evaluate to true on a parent node but false
/// on a child if the parent has observers but the child does not.
///
/// See also:
///
/// * [Layer.addCompositionCallback].
bool
get
subtreeHasCompositionCallbacks
=>
_compositionCallbackCount
>
0
;
int
_compositionCallbackCount
=
0
;
void
_updateSubtreeCompositionObserverCount
(
int
delta
)
{
assert
(
delta
!=
0
);
_compositionCallbackCount
+=
delta
;
assert
(
_compositionCallbackCount
>=
0
);
if
(
parent
!=
null
)
{
parent
!.
_updateSubtreeCompositionObserverCount
(
delta
);
}
}
void
_fireCompositionCallbacks
({
required
bool
includeChildren
})
{
for
(
final
VoidCallback
callback
in
List
<
VoidCallback
>.
of
(
_callbacks
.
values
))
{
callback
();
}
}
bool
_debugMutationsLocked
=
false
;
/// Describes the clip that would be applied to contents of this layer,
/// if any.
Rect
?
describeClipBounds
()
=>
null
;
/// Adds a callback for when the layer tree that this layer is part of gets
/// composited, or when it is detached and will not be rendered again.
///
/// This callback will fire even if an ancestor layer is added with retained
/// rendering, meaning that it will fire even if this layer gets added to the
/// scene via some call to [ui.SceneBuilder.addRetained] on one of its
/// ancestor layers.
///
/// The callback receives a reference to this layer. The recipient must not
/// mutate the layer during the scope of the callback, but may traverse the
/// tree to find information about the current transform or clip. The layer
/// may not be [attached] anymore in this state, but even if it is detached it
/// may still have an also detached parent it can visit.
///
/// If new callbacks are added or removed within the [callback], the new
/// callbacks will fire (or stop firing) on the _next_ compositing event.
///
/// {@template flutter.rendering.Layer.compositionCallbacks}
/// Composition callbacks are useful in place of pushing a layer that would
/// otherwise try to observe the layer tree without actually affecting
/// compositing. For example, a composition callback may be used to observe
/// the total transform and clip of the current container layer to determine
/// whether a render object drawn into it is visible or not.
///
/// Calling the returned callback will remove [callback] from the composition
/// callbacks.
/// {@endtemplate}
VoidCallback
addCompositionCallback
(
CompositionCallback
callback
)
{
_updateSubtreeCompositionObserverCount
(
1
);
final
int
callbackId
=
_nextCallbackId
+=
1
;
_callbacks
[
callbackId
]
=
()
{
assert
(()
{
_debugMutationsLocked
=
true
;
return
true
;
}());
callback
(
this
);
assert
(()
{
_debugMutationsLocked
=
false
;
return
true
;
}());
};
return
()
{
assert
(
_callbacks
.
containsKey
(
callbackId
));
_callbacks
.
remove
(
callbackId
);
_updateSubtreeCompositionObserverCount
(-
1
);
};
}
/// If asserts are enabled, returns whether [dispose] has
/// been called since the last time any retained resources were created.
///
...
...
@@ -164,6 +249,7 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
/// Called by [LayerHandle].
void
_unref
()
{
assert
(!
_debugMutationsLocked
);
assert
(
_refCount
>
0
);
_refCount
-=
1
;
if
(
_refCount
==
0
)
{
...
...
@@ -205,6 +291,7 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
@protected
@visibleForTesting
void
dispose
()
{
assert
(!
_debugMutationsLocked
);
assert
(
!
_debugDisposed
,
'Layers must only be disposed once. This is typically handled by '
...
...
@@ -261,6 +348,7 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
@protected
@visibleForTesting
void
markNeedsAddToScene
()
{
assert
(!
_debugMutationsLocked
);
assert
(
!
alwaysNeedsAddToScene
,
'
$runtimeType
with alwaysNeedsAddToScene set called markNeedsAddToScene.
\n
'
...
...
@@ -282,6 +370,7 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
/// this method has no effect.
@visibleForTesting
void
debugMarkClean
()
{
assert
(!
_debugMutationsLocked
);
assert
(()
{
_needsAddToScene
=
false
;
return
true
;
...
...
@@ -331,6 +420,7 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
@protected
@visibleForTesting
set
engineLayer
(
ui
.
EngineLayer
?
value
)
{
assert
(!
_debugMutationsLocked
);
assert
(!
_debugDisposed
);
_engineLayer
?.
dispose
();
...
...
@@ -375,6 +465,7 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
@protected
@visibleForTesting
void
updateSubtreeNeedsAddToScene
()
{
assert
(!
_debugMutationsLocked
);
_needsAddToScene
=
_needsAddToScene
||
alwaysNeedsAddToScene
;
}
...
...
@@ -387,18 +478,26 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
Layer
?
_previousSibling
;
@override
void
dropChild
(
AbstractNode
child
)
{
void
dropChild
(
Layer
child
)
{
assert
(!
_debugMutationsLocked
);
if
(!
alwaysNeedsAddToScene
)
{
markNeedsAddToScene
();
}
if
(
child
.
_compositionCallbackCount
!=
0
)
{
_updateSubtreeCompositionObserverCount
(-
child
.
_compositionCallbackCount
);
}
super
.
dropChild
(
child
);
}
@override
void
adoptChild
(
AbstractNode
child
)
{
void
adoptChild
(
Layer
child
)
{
assert
(!
_debugMutationsLocked
);
if
(!
alwaysNeedsAddToScene
)
{
markNeedsAddToScene
();
}
if
(
child
.
_compositionCallbackCount
!=
0
)
{
_updateSubtreeCompositionObserverCount
(
child
.
_compositionCallbackCount
);
}
super
.
adoptChild
(
child
);
}
...
...
@@ -407,6 +506,7 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
/// This has no effect if the layer's parent is already null.
@mustCallSuper
void
remove
()
{
assert
(!
_debugMutationsLocked
);
parent
?.
_removeChild
(
this
);
}
...
...
@@ -529,6 +629,7 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
void
addToScene
(
ui
.
SceneBuilder
builder
);
void
_addToSceneWithRetainedRendering
(
ui
.
SceneBuilder
builder
)
{
assert
(!
_debugMutationsLocked
);
// There can't be a loop by adding a retained layer subtree whose
// _needsAddToScene is false.
//
...
...
@@ -909,12 +1010,28 @@ class PerformanceOverlayLayer extends Layer {
}
}
/// The signature of the callback added in [Layer.addCompositionCallback].
typedef
CompositionCallback
=
void
Function
(
Layer
);
/// A composited layer that has a list of children.
///
/// A [ContainerLayer] instance merely takes a list of children and inserts them
/// into the composited rendering in order. There are subclasses of
/// [ContainerLayer] which apply more elaborate effects in the process.
class
ContainerLayer
extends
Layer
{
@override
void
_fireCompositionCallbacks
({
required
bool
includeChildren
})
{
super
.
_fireCompositionCallbacks
(
includeChildren:
includeChildren
);
if
(!
includeChildren
)
{
return
;
}
Layer
?
child
=
firstChild
;
while
(
child
!=
null
)
{
child
.
_fireCompositionCallbacks
(
includeChildren:
includeChildren
);
child
=
child
.
nextSibling
;
}
}
/// The first composited layer in this layer's child list.
Layer
?
get
firstChild
=>
_firstChild
;
Layer
?
_firstChild
;
...
...
@@ -935,6 +1052,9 @@ class ContainerLayer extends Layer {
ui
.
Scene
buildScene
(
ui
.
SceneBuilder
builder
)
{
updateSubtreeNeedsAddToScene
();
addToScene
(
builder
);
if
(
subtreeHasCompositionCallbacks
)
{
_fireCompositionCallbacks
(
includeChildren:
true
);
}
// Clearing the flag _after_ calling `addToScene`, not _before_. This is
// because `addToScene` calls children's `addToScene` methods, which may
// mark this layer as dirty.
...
...
@@ -966,6 +1086,7 @@ class ContainerLayer extends Layer {
@override
void
dispose
()
{
removeAllChildren
();
_callbacks
.
clear
();
super
.
dispose
();
}
...
...
@@ -994,6 +1115,7 @@ class ContainerLayer extends Layer {
@override
void
attach
(
Object
owner
)
{
assert
(!
_debugMutationsLocked
);
super
.
attach
(
owner
);
Layer
?
child
=
firstChild
;
while
(
child
!=
null
)
{
...
...
@@ -1004,16 +1126,25 @@ class ContainerLayer extends Layer {
@override
void
detach
()
{
assert
(!
_debugMutationsLocked
);
super
.
detach
();
Layer
?
child
=
firstChild
;
while
(
child
!=
null
)
{
child
.
detach
();
child
=
child
.
nextSibling
;
}
// Detach indicates that we may never be composited again. Clients
// interested in observing composition need to get an update here because
// they might otherwise never get another one even though the layer is no
// longer visible.
//
// Children fired them already in child.detach().
_fireCompositionCallbacks
(
includeChildren:
false
);
}
/// Adds the given layer to the end of this layer's child list.
void
append
(
Layer
child
)
{
assert
(!
_debugMutationsLocked
);
assert
(
child
!=
this
);
assert
(
child
!=
firstChild
);
assert
(
child
!=
lastChild
);
...
...
@@ -1072,6 +1203,7 @@ class ContainerLayer extends Layer {
/// Removes all of this layer's children from its child list.
void
removeAllChildren
()
{
assert
(!
_debugMutationsLocked
);
Layer
?
child
=
firstChild
;
while
(
child
!=
null
)
{
final
Layer
?
next
=
child
.
nextSibling
;
...
...
@@ -1221,7 +1353,7 @@ class OffsetLayer extends ContainerLayer {
void
applyTransform
(
Layer
?
child
,
Matrix4
transform
)
{
assert
(
child
!=
null
);
assert
(
transform
!=
null
);
transform
.
multiply
(
Matrix4
.
translationValues
(
offset
.
dx
,
offset
.
dy
,
0.0
)
);
transform
.
translate
(
offset
.
dx
,
offset
.
dy
);
}
@override
...
...
@@ -1321,6 +1453,9 @@ class ClipRectLayer extends ContainerLayer {
}
}
@override
Rect
?
describeClipBounds
()
=>
clipRect
;
/// {@template flutter.rendering.ClipRectLayer.clipBehavior}
/// Controls how to clip.
///
...
...
@@ -1408,6 +1543,9 @@ class ClipRRectLayer extends ContainerLayer {
}
}
@override
Rect
?
describeClipBounds
()
=>
clipRRect
?.
outerRect
;
/// {@macro flutter.rendering.ClipRectLayer.clipBehavior}
///
/// Defaults to [Clip.antiAlias].
...
...
@@ -1491,6 +1629,9 @@ class ClipPathLayer extends ContainerLayer {
}
}
@override
Rect
?
describeClipBounds
()
=>
clipPath
?.
getBounds
();
/// {@macro flutter.rendering.ClipRectLayer.clipBehavior}
///
/// Defaults to [Clip.antiAlias].
...
...
packages/flutter/lib/src/rendering/object.dart
View file @
d54cdf9e
...
...
@@ -12,7 +12,6 @@ import 'package:flutter/painting.dart';
import
'package:flutter/semantics.dart'
;
import
'package:vector_math/vector_math_64.dart'
;
import
'binding.dart'
;
import
'debug.dart'
;
import
'layer.dart'
;
...
...
@@ -326,6 +325,22 @@ class PaintingContext extends ClipContext {
_containerLayer
.
append
(
_currentLayer
!);
}
/// Adds a [CompositionCallback] for the current [ContainerLayer] used by this
/// context.
///
/// Composition callbacks are called whenever the layer tree containing the
/// current layer of this painting context gets composited, or when it gets
/// detached and will not be rendered again. This happens regardless of
/// whether the layer is added via retained rendering or not.
///
/// {@macro flutter.rendering.Layer.compositionCallbacks}
///
/// See also:
/// * [Layer.addCompositionCallback].
VoidCallback
addCompositionCallback
(
CompositionCallback
callback
)
{
return
_containerLayer
.
addCompositionCallback
(
callback
);
}
/// Stop recording to a canvas if recording has started.
///
/// Do not call this function directly: functions in this class will call
...
...
packages/flutter/test/rendering/layers_test.dart
View file @
d54cdf9e
...
...
@@ -716,6 +716,255 @@ void main() {
layer
.
addToScene
(
builder
);
expect
(
layer
.
engineLayer
,
null
);
});
test
(
'Layers describe clip bounds'
,
()
{
ContainerLayer
layer
=
ContainerLayer
();
expect
(
layer
.
describeClipBounds
(),
null
);
const
Rect
bounds
=
Rect
.
fromLTRB
(
10
,
10
,
20
,
20
);
final
RRect
rbounds
=
RRect
.
fromRectXY
(
bounds
,
2
,
2
);
layer
=
ClipRectLayer
(
clipRect:
bounds
);
expect
(
layer
.
describeClipBounds
(),
bounds
);
layer
=
ClipRRectLayer
(
clipRRect:
rbounds
);
expect
(
layer
.
describeClipBounds
(),
rbounds
.
outerRect
);
layer
=
ClipPathLayer
(
clipPath:
Path
()..
addRect
(
bounds
));
expect
(
layer
.
describeClipBounds
(),
bounds
);
});
test
(
'Subtree has composition callbacks'
,
()
{
final
ContainerLayer
root
=
ContainerLayer
();
expect
(
root
.
subtreeHasCompositionCallbacks
,
false
);
final
List
<
VoidCallback
>
cancellationCallbacks
=
<
VoidCallback
>[];
cancellationCallbacks
.
add
(
root
.
addCompositionCallback
((
_
)
{}));
expect
(
root
.
subtreeHasCompositionCallbacks
,
true
);
final
ContainerLayer
a1
=
ContainerLayer
();
final
ContainerLayer
a2
=
ContainerLayer
();
final
ContainerLayer
b1
=
ContainerLayer
();
root
.
append
(
a1
);
root
.
append
(
a2
);
a1
.
append
(
b1
);
expect
(
root
.
subtreeHasCompositionCallbacks
,
true
);
expect
(
a1
.
subtreeHasCompositionCallbacks
,
false
);
expect
(
a2
.
subtreeHasCompositionCallbacks
,
false
);
expect
(
b1
.
subtreeHasCompositionCallbacks
,
false
);
cancellationCallbacks
.
add
(
b1
.
addCompositionCallback
((
_
)
{}));
expect
(
root
.
subtreeHasCompositionCallbacks
,
true
);
expect
(
a1
.
subtreeHasCompositionCallbacks
,
true
);
expect
(
a2
.
subtreeHasCompositionCallbacks
,
false
);
expect
(
b1
.
subtreeHasCompositionCallbacks
,
true
);
cancellationCallbacks
.
removeAt
(
0
)();
expect
(
root
.
subtreeHasCompositionCallbacks
,
true
);
expect
(
a1
.
subtreeHasCompositionCallbacks
,
true
);
expect
(
a2
.
subtreeHasCompositionCallbacks
,
false
);
expect
(
b1
.
subtreeHasCompositionCallbacks
,
true
);
cancellationCallbacks
.
removeAt
(
0
)();
expect
(
root
.
subtreeHasCompositionCallbacks
,
false
);
expect
(
a1
.
subtreeHasCompositionCallbacks
,
false
);
expect
(
a2
.
subtreeHasCompositionCallbacks
,
false
);
expect
(
b1
.
subtreeHasCompositionCallbacks
,
false
);
});
test
(
'Subtree has composition callbacks - removeChild'
,
()
{
final
ContainerLayer
root
=
ContainerLayer
();
expect
(
root
.
subtreeHasCompositionCallbacks
,
false
);
final
ContainerLayer
a1
=
ContainerLayer
();
final
ContainerLayer
a2
=
ContainerLayer
();
final
ContainerLayer
b1
=
ContainerLayer
();
root
.
append
(
a1
);
root
.
append
(
a2
);
a1
.
append
(
b1
);
expect
(
b1
.
subtreeHasCompositionCallbacks
,
false
);
expect
(
a1
.
subtreeHasCompositionCallbacks
,
false
);
expect
(
root
.
subtreeHasCompositionCallbacks
,
false
);
expect
(
a2
.
subtreeHasCompositionCallbacks
,
false
);
b1
.
addCompositionCallback
((
_
)
{
});
expect
(
b1
.
subtreeHasCompositionCallbacks
,
true
);
expect
(
a1
.
subtreeHasCompositionCallbacks
,
true
);
expect
(
root
.
subtreeHasCompositionCallbacks
,
true
);
expect
(
a2
.
subtreeHasCompositionCallbacks
,
false
);
b1
.
remove
();
expect
(
b1
.
subtreeHasCompositionCallbacks
,
true
);
expect
(
a1
.
subtreeHasCompositionCallbacks
,
false
);
expect
(
root
.
subtreeHasCompositionCallbacks
,
false
);
expect
(
a2
.
subtreeHasCompositionCallbacks
,
false
);
});
test
(
'No callback if removed'
,
()
{
final
ContainerLayer
root
=
ContainerLayer
();
final
ContainerLayer
a1
=
ContainerLayer
();
final
ContainerLayer
a2
=
ContainerLayer
();
final
ContainerLayer
b1
=
ContainerLayer
();
root
.
append
(
a1
);
root
.
append
(
a2
);
a1
.
append
(
b1
);
// Add and immediately remove the callback.
b1
.
addCompositionCallback
((
Layer
layer
)
{
fail
(
'Should not have called back'
);
})();
root
.
buildScene
(
SceneBuilder
()).
dispose
();
});
test
(
'Observe layer tree composition - not retained'
,
()
{
final
ContainerLayer
root
=
ContainerLayer
();
final
ContainerLayer
a1
=
ContainerLayer
();
final
ContainerLayer
a2
=
ContainerLayer
();
final
ContainerLayer
b1
=
ContainerLayer
();
root
.
append
(
a1
);
root
.
append
(
a2
);
a1
.
append
(
b1
);
bool
compositedB1
=
false
;
b1
.
addCompositionCallback
((
Layer
layer
)
{
expect
(
layer
,
b1
);
compositedB1
=
true
;
});
expect
(
compositedB1
,
false
);
root
.
buildScene
(
SceneBuilder
()).
dispose
();
expect
(
compositedB1
,
true
);
});
test
(
'Observe layer tree composition - retained'
,
()
{
final
ContainerLayer
root
=
ContainerLayer
();
final
ContainerLayer
a1
=
ContainerLayer
();
final
ContainerLayer
a2
=
ContainerLayer
();
final
ContainerLayer
b1
=
ContainerLayer
();
root
.
append
(
a1
);
root
.
append
(
a2
);
a1
.
append
(
b1
);
// Actually build the retained layer so that the engine sees it as real and
// reusable.
SceneBuilder
builder
=
SceneBuilder
();
b1
.
engineLayer
=
builder
.
pushOffset
(
0
,
0
);
builder
.
build
().
dispose
();
builder
=
SceneBuilder
();
// Force the layer to appear clean and have an engine layer for retained
// rendering.
expect
(
b1
.
engineLayer
,
isNotNull
);
b1
.
debugMarkClean
();
expect
(
b1
.
debugSubtreeNeedsAddToScene
,
false
);
bool
compositedB1
=
false
;
b1
.
addCompositionCallback
((
Layer
layer
)
{
expect
(
layer
,
b1
);
compositedB1
=
true
;
});
expect
(
compositedB1
,
false
);
root
.
buildScene
(
builder
).
dispose
();
expect
(
compositedB1
,
true
);
});
test
(
'Observe layer tree composition - asserts on mutation'
,
()
{
final
ContainerLayer
root
=
ContainerLayer
();
final
ContainerLayer
a1
=
ContainerLayer
();
final
ContainerLayer
a2
=
ContainerLayer
();
final
ContainerLayer
b1
=
ContainerLayer
();
root
.
append
(
a1
);
root
.
append
(
a2
);
a1
.
append
(
b1
);
bool
compositedB1
=
false
;
b1
.
addCompositionCallback
((
Layer
layer
)
{
expect
(
layer
,
b1
);
expect
(()
=>
layer
.
remove
(),
throwsAssertionError
);
expect
(()
=>
layer
.
dispose
(),
throwsAssertionError
);
expect
(()
=>
layer
.
markNeedsAddToScene
(),
throwsAssertionError
);
expect
(()
=>
layer
.
debugMarkClean
(),
throwsAssertionError
);
expect
(()
=>
layer
.
updateSubtreeNeedsAddToScene
(),
throwsAssertionError
);
expect
(()
=>
layer
.
dropChild
(
ContainerLayer
()),
throwsAssertionError
);
expect
(()
=>
layer
.
adoptChild
(
ContainerLayer
()),
throwsAssertionError
);
expect
(()
=>
(
layer
as
ContainerLayer
).
append
(
ContainerLayer
()),
throwsAssertionError
);
expect
(()
=>
layer
.
engineLayer
=
null
,
throwsAssertionError
);
compositedB1
=
true
;
});
expect
(
compositedB1
,
false
);
root
.
buildScene
(
SceneBuilder
()).
dispose
();
expect
(
compositedB1
,
true
);
});
test
(
'Observe layer tree composition - detach triggers callback'
,
()
{
final
ContainerLayer
root
=
ContainerLayer
();
final
ContainerLayer
a1
=
ContainerLayer
();
final
ContainerLayer
a2
=
ContainerLayer
();
final
ContainerLayer
b1
=
ContainerLayer
();
root
.
append
(
a1
);
root
.
append
(
a2
);
a1
.
append
(
b1
);
bool
compositedB1
=
false
;
b1
.
addCompositionCallback
((
Layer
layer
)
{
expect
(
layer
,
b1
);
compositedB1
=
true
;
});
root
.
attach
(
Object
());
expect
(
compositedB1
,
false
);
root
.
detach
();
expect
(
compositedB1
,
true
);
});
test
(
'Observe layer tree composition - observer count correctly maintained'
,
()
{
final
ContainerLayer
root
=
ContainerLayer
();
final
ContainerLayer
a1
=
ContainerLayer
();
root
.
append
(
a1
);
expect
(
root
.
subtreeHasCompositionCallbacks
,
false
);
expect
(
a1
.
subtreeHasCompositionCallbacks
,
false
);
final
VoidCallback
remover1
=
a1
.
addCompositionCallback
((
_
)
{
});
final
VoidCallback
remover2
=
a1
.
addCompositionCallback
((
_
)
{
});
expect
(
root
.
subtreeHasCompositionCallbacks
,
true
);
expect
(
a1
.
subtreeHasCompositionCallbacks
,
true
);
remover1
();
expect
(
root
.
subtreeHasCompositionCallbacks
,
true
);
expect
(
a1
.
subtreeHasCompositionCallbacks
,
true
);
remover2
();
expect
(
root
.
subtreeHasCompositionCallbacks
,
false
);
expect
(
a1
.
subtreeHasCompositionCallbacks
,
false
);
});
}
class
FakeEngineLayer
extends
Fake
implements
EngineLayer
{
...
...
packages/flutter/test/rendering/object_test.dart
View file @
d54cdf9e
...
...
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:ui'
as
ui
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
;
...
...
@@ -246,8 +248,39 @@ void main() {
renderObject
.
dispose
();
expect
(
renderObject
.
debugLayer
,
null
);
});
test
(
'Add composition callback works'
,
()
{
final
ContainerLayer
root
=
ContainerLayer
();
final
PaintingContext
context
=
PaintingContext
(
root
,
Rect
.
zero
);
bool
calledBack
=
false
;
final
TestObservingRenderObject
object
=
TestObservingRenderObject
((
Layer
layer
)
{
expect
(
layer
,
root
);
calledBack
=
true
;
});
expect
(
calledBack
,
false
);
object
.
paint
(
context
,
Offset
.
zero
);
expect
(
calledBack
,
false
);
root
.
buildScene
(
ui
.
SceneBuilder
()).
dispose
();
expect
(
calledBack
,
true
);
});
}
class
TestObservingRenderObject
extends
RenderBox
{
TestObservingRenderObject
(
this
.
callback
);
final
CompositionCallback
callback
;
@override
bool
get
sizedByParent
=>
true
;
@override
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
context
.
addCompositionCallback
(
callback
);
}
}
// Tests the create-update cycle by pumping two frames. The first frame has no
// prior layer and forces the painting context to create a new one. The second
// frame reuses the layer painted on the first frame.
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment