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
fe87538b
Unverified
Commit
fe87538b
authored
May 16, 2022
by
Dan Field
Committed by
GitHub
May 16, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implement paintsChild on RenderObjects that skip painting on their children (#103768)
parent
6e7f7aea
Changes
7
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
220 additions
and
13 deletions
+220
-13
material.dart
packages/flutter/lib/src/material/material.dart
+9
-0
object.dart
packages/flutter/lib/src/rendering/object.dart
+25
-0
proxy_box.dart
packages/flutter/lib/src/rendering/proxy_box.dart
+25
-2
sliver_multi_box_adaptor.dart
...s/flutter/lib/src/rendering/sliver_multi_box_adaptor.dart
+15
-11
material_test.dart
packages/flutter/test/material/material_test.dart
+48
-0
proxy_box_test.dart
packages/flutter/test/rendering/proxy_box_test.dart
+60
-0
sliver_fixed_extent_layout_test.dart
...utter/test/rendering/sliver_fixed_extent_layout_test.dart
+38
-0
No files found.
packages/flutter/lib/src/material/material.dart
View file @
fe87538b
...
...
@@ -682,7 +682,16 @@ abstract class InkFeature {
final
List
<
RenderObject
>
descendants
=
<
RenderObject
>[
referenceBox
];
RenderObject
node
=
referenceBox
;
while
(
node
!=
_controller
)
{
final
RenderObject
childNode
=
node
;
node
=
node
.
parent
!
as
RenderObject
;
if
(!
node
.
paintsChild
(
childNode
))
{
// Some node between the reference box and this would skip painting on
// the reference box, so bail out early and avoid unnecessary painting.
// Some cases where this can happen are the reference box being
// offstage, in a fully transparent opacity node, or in a keep alive
// bucket.
return
;
}
descendants
.
add
(
node
);
}
// determine the transform that gets our coordinate system to be like theirs
...
...
packages/flutter/lib/src/rendering/object.dart
View file @
fe87538b
...
...
@@ -2701,10 +2701,35 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
///
/// Used by coordinate conversion functions to translate coordinates local to
/// one render object into coordinates local to another render object.
///
/// Some RenderObjects will provide a zeroed out matrix in this method,
/// indicating that the child should not paint anything or respond to hit
/// tests currently. A parent may supply a non-zero matrix even though it
/// does not paint its child currently, for example if the parent is a
/// [RenderOffstage] with `offstage` set to true. In both of these cases,
/// the parent must return `false` from [paintsChild].
void
applyPaintTransform
(
covariant
RenderObject
child
,
Matrix4
transform
)
{
assert
(
child
.
parent
==
this
);
}
/// Whether the given child would be painted if [paint] were called.
///
/// Some RenderObjects skip painting their children if they are configured to
/// not produce any visible effects. For example, a [RenderOffstage] with
/// its `offstage` property set to true, or a [RenderOpacity] with its opacity
/// value set to zero.
///
/// In these cases, the parent may still supply a non-zero matrix in
/// [applyPaintTransform] to inform callers about where it would paint the
/// child if the child were painted at all. Alternatively, the parent may
/// supply a zeroed out matrix if it would not otherwise be able to determine
/// a valid matrix for the child and thus cannot meaningfully determine where
/// the child would paint.
bool
paintsChild
(
covariant
RenderObject
child
)
{
assert
(
child
.
parent
==
this
);
return
true
;
}
/// Applies the paint transform up the tree to `ancestor`.
///
/// Returns a matrix that maps the local paint coordinate system to the
...
...
packages/flutter/lib/src/rendering/proxy_box.dart
View file @
fe87538b
...
...
@@ -896,6 +896,12 @@ class RenderOpacity extends RenderProxyBox {
markNeedsSemanticsUpdate
();
}
@override
bool
paintsChild
(
RenderBox
child
)
{
assert
(
child
.
parent
==
this
);
return
_alpha
>
0
;
}
@override
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
if
(
child
!=
null
)
{
...
...
@@ -1014,6 +1020,12 @@ mixin RenderAnimatedOpacityMixin<T extends RenderObject> on RenderObjectWithChil
}
}
@override
bool
paintsChild
(
RenderObject
child
)
{
assert
(
child
.
parent
==
this
);
return
opacity
.
value
>
0
;
}
@override
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
if
(
_alpha
==
0
)
{
...
...
@@ -2805,9 +2817,15 @@ class RenderFittedBox extends RenderProxyBox {
);
}
@override
bool
paintsChild
(
RenderBox
child
)
{
assert
(
child
.
parent
==
this
);
return
!
size
.
isEmpty
&&
!
child
.
size
.
isEmpty
;
}
@override
void
applyPaintTransform
(
RenderBox
child
,
Matrix4
transform
)
{
if
(
size
.
isEmpty
||
child
.
size
.
isEmpty
)
{
if
(
!
paintsChild
(
child
)
)
{
transform
.
setZero
();
}
else
{
_updatePaintData
();
...
...
@@ -3575,7 +3593,6 @@ class RenderOffstage extends RenderProxyBox {
return
super
.
computeDryLayout
(
constraints
);
}
@override
void
performResize
()
{
assert
(
offstage
);
...
...
@@ -3596,6 +3613,12 @@ class RenderOffstage extends RenderProxyBox {
return
!
offstage
&&
super
.
hitTest
(
result
,
position:
position
);
}
@override
bool
paintsChild
(
RenderBox
child
)
{
assert
(
child
.
parent
==
this
);
return
!
offstage
;
}
@override
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
if
(
offstage
)
...
...
packages/flutter/lib/src/rendering/sliver_multi_box_adaptor.dart
View file @
fe87538b
...
...
@@ -575,19 +575,23 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
return
childParentData
.
layoutOffset
;
}
@override
bool
paintsChild
(
RenderBox
child
)
{
final
SliverMultiBoxAdaptorParentData
?
childParentData
=
child
.
parentData
as
SliverMultiBoxAdaptorParentData
?;
return
childParentData
?.
index
!=
null
&&
!
_keepAliveBucket
.
containsKey
(
childParentData
!.
index
);
}
@override
void
applyPaintTransform
(
RenderBox
child
,
Matrix4
transform
)
{
final
SliverMultiBoxAdaptorParentData
childParentData
=
child
.
parentData
!
as
SliverMultiBoxAdaptorParentData
;
if
(
childParentData
.
index
==
null
)
{
// If the child has no index, such as with the prototype of a
// SliverPrototypeExtentList, then it is not visible, so we give it a
// zero transform to prevent it from painting.
transform
.
setZero
();
}
else
if
(
_keepAliveBucket
.
containsKey
(
childParentData
.
index
))
{
// It is possible that widgets under kept alive children want to paint
// themselves. For example, the Material widget tries to paint all
// InkFeatures under its subtree as long as they are not disposed. In
// such case, we give it a zero transform to prevent them from painting.
if
(!
paintsChild
(
child
))
{
// This can happen if some child asks for the global transform even though
// they are not getting painted. In that case, the transform sets set to
// zero since [applyPaintTransformForBoxChild] would end up throwing due
// to the child not being configured correctly for applying a transform.
// There's no assert here because asking for the paint transform is a
// valid thing to do even if a child would not be painted, but there is no
// meaningful non-zero matrix to use in this case.
transform
.
setZero
();
}
else
{
applyPaintTransformForBoxChild
(
child
,
transform
);
...
...
packages/flutter/test/material/material_test.dart
View file @
fe87538b
...
...
@@ -927,4 +927,52 @@ void main() {
);
});
});
testWidgets
(
'InkFeature skips painting if intermediate node skips'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
sizedBoxKey
=
GlobalKey
();
final
GlobalKey
materialKey
=
GlobalKey
();
await
tester
.
pumpWidget
(
Material
(
key:
materialKey
,
child:
Offstage
(
child:
SizedBox
(
key:
sizedBoxKey
,
width:
20
,
height:
20
),
),
));
final
MaterialInkController
controller
=
Material
.
of
(
sizedBoxKey
.
currentContext
!)!;
final
TrackPaintInkFeature
tracker
=
TrackPaintInkFeature
(
controller:
controller
,
referenceBox:
sizedBoxKey
.
currentContext
!.
findRenderObject
()!
as
RenderBox
,
);
controller
.
addInkFeature
(
tracker
);
expect
(
tracker
.
paintCount
,
0
);
// Force a repaint. Since it's offstage, the ink feture should not get painted.
materialKey
.
currentContext
!.
findRenderObject
()!.
paint
(
PaintingContext
(
ContainerLayer
(),
Rect
.
largest
),
Offset
.
zero
);
expect
(
tracker
.
paintCount
,
0
);
await
tester
.
pumpWidget
(
Material
(
key:
materialKey
,
child:
Offstage
(
offstage:
false
,
child:
SizedBox
(
key:
sizedBoxKey
,
width:
20
,
height:
20
),
),
));
// Gets a paint because the global keys have reused the elements and it is
// now onstage.
expect
(
tracker
.
paintCount
,
1
);
// Force a repaint again. This time, it gets repainted because it is onstage.
materialKey
.
currentContext
!.
findRenderObject
()!.
paint
(
PaintingContext
(
ContainerLayer
(),
Rect
.
largest
),
Offset
.
zero
);
expect
(
tracker
.
paintCount
,
2
);
});
}
class
TrackPaintInkFeature
extends
InkFeature
{
TrackPaintInkFeature
({
required
super
.
controller
,
required
super
.
referenceBox
});
int
paintCount
=
0
;
@override
void
paintFeature
(
Canvas
canvas
,
Matrix4
transform
)
{
paintCount
+=
1
;
}
}
packages/flutter/test/rendering/proxy_box_test.dart
View file @
fe87538b
...
...
@@ -687,6 +687,66 @@ void main() {
expect
(()
=>
pumpFrame
(
phase:
EnginePhase
.
composite
),
returnsNormally
);
});
test
(
'Offstage implements paintsChild correctly'
,
()
{
final
RenderBox
box
=
RenderConstrainedBox
(
additionalConstraints:
const
BoxConstraints
.
tightFor
(
width:
20
));
final
RenderBox
parent
=
RenderConstrainedBox
(
additionalConstraints:
const
BoxConstraints
.
tightFor
(
width:
20
));
final
RenderOffstage
offstage
=
RenderOffstage
(
offstage:
false
,
child:
box
);
parent
.
adoptChild
(
offstage
);
expect
(
offstage
.
paintsChild
(
box
),
true
);
offstage
.
offstage
=
true
;
expect
(
offstage
.
paintsChild
(
box
),
false
);
});
test
(
'Opacity implements paintsChild correctly'
,
()
{
final
RenderBox
box
=
RenderConstrainedBox
(
additionalConstraints:
const
BoxConstraints
.
tightFor
(
width:
20
));
final
RenderBox
parent
=
RenderConstrainedBox
(
additionalConstraints:
const
BoxConstraints
.
tightFor
(
width:
20
));
final
RenderOpacity
opacity
=
RenderOpacity
(
child:
box
);
parent
.
adoptChild
(
opacity
);
expect
(
opacity
.
paintsChild
(
box
),
true
);
opacity
.
opacity
=
0
;
expect
(
opacity
.
paintsChild
(
box
),
false
);
});
test
(
'AnimatedOpacity sets paint matrix to zero when alpha == 0'
,
()
{
final
RenderBox
box
=
RenderConstrainedBox
(
additionalConstraints:
const
BoxConstraints
.
tightFor
(
width:
20
));
final
RenderBox
parent
=
RenderConstrainedBox
(
additionalConstraints:
const
BoxConstraints
.
tightFor
(
width:
20
));
final
AnimationController
opacityAnimation
=
AnimationController
(
value:
1
,
vsync:
FakeTickerProvider
());
final
RenderAnimatedOpacity
opacity
=
RenderAnimatedOpacity
(
opacity:
opacityAnimation
,
child:
box
);
parent
.
adoptChild
(
opacity
);
// Make it listen to the animation.
opacity
.
attach
(
PipelineOwner
());
expect
(
opacity
.
paintsChild
(
box
),
true
);
opacityAnimation
.
value
=
0
;
expect
(
opacity
.
paintsChild
(
box
),
false
);
});
test
(
'AnimatedOpacity sets paint matrix to zero when alpha == 0 (sliver)'
,
()
{
final
RenderSliver
sliver
=
RenderSliverToBoxAdapter
(
child:
RenderConstrainedBox
(
additionalConstraints:
const
BoxConstraints
.
tightFor
(
width:
20
)));
final
RenderBox
parent
=
RenderConstrainedBox
(
additionalConstraints:
const
BoxConstraints
.
tightFor
(
width:
20
));
final
AnimationController
opacityAnimation
=
AnimationController
(
value:
1
,
vsync:
FakeTickerProvider
());
final
RenderSliverAnimatedOpacity
opacity
=
RenderSliverAnimatedOpacity
(
opacity:
opacityAnimation
,
sliver:
sliver
);
parent
.
adoptChild
(
opacity
);
// Make it listen to the animation.
opacity
.
attach
(
PipelineOwner
());
expect
(
opacity
.
paintsChild
(
sliver
),
true
);
opacityAnimation
.
value
=
0
;
expect
(
opacity
.
paintsChild
(
sliver
),
false
);
});
test
(
'RenderCustomClip extenders respect clipBehavior when asked to describeApproximateClip'
,
()
{
final
RenderBox
child
=
RenderConstrainedBox
(
additionalConstraints:
const
BoxConstraints
.
tightFor
(
width:
200
,
height:
200
));
final
RenderClipRect
renderClipRect
=
RenderClipRect
(
clipBehavior:
Clip
.
none
,
child:
child
);
...
...
packages/flutter/test/rendering/sliver_fixed_extent_layout_test.dart
View file @
fe87538b
...
...
@@ -110,6 +110,44 @@ void main() {
expect
(
actual
,
0
);
});
});
test
(
'Implements paintsChild correctly'
,
()
{
final
List
<
RenderBox
>
children
=
<
RenderBox
>[
RenderSizedBox
(
const
Size
(
400.0
,
100.0
)),
RenderSizedBox
(
const
Size
(
400.0
,
100.0
)),
RenderSizedBox
(
const
Size
(
400.0
,
100.0
)),
];
final
TestRenderSliverBoxChildManager
childManager
=
TestRenderSliverBoxChildManager
(
children:
children
,
);
final
RenderViewport
root
=
RenderViewport
(
crossAxisDirection:
AxisDirection
.
right
,
offset:
ViewportOffset
.
zero
(),
cacheExtent:
0
,
children:
<
RenderSliver
>[
childManager
.
createRenderSliverFillViewport
(),
],
);
layout
(
root
);
expect
(
children
.
first
.
parent
,
isA
<
RenderSliverMultiBoxAdaptor
>());
final
RenderSliverMultiBoxAdaptor
parent
=
children
.
first
.
parent
!
as
RenderSliverMultiBoxAdaptor
;
expect
(
parent
.
paintsChild
(
children
[
0
]),
true
);
expect
(
parent
.
paintsChild
(
children
[
1
]),
false
);
expect
(
parent
.
paintsChild
(
children
[
2
]),
false
);
root
.
offset
=
ViewportOffset
.
fixed
(
600
);
pumpFrame
();
expect
(
parent
.
paintsChild
(
children
[
0
]),
false
);
expect
(
parent
.
paintsChild
(
children
[
1
]),
true
);
expect
(
parent
.
paintsChild
(
children
[
2
]),
false
);
root
.
offset
=
ViewportOffset
.
fixed
(
1200
);
pumpFrame
();
expect
(
parent
.
paintsChild
(
children
[
0
]),
false
);
expect
(
parent
.
paintsChild
(
children
[
1
]),
false
);
expect
(
parent
.
paintsChild
(
children
[
2
]),
true
);
});
}
int
testGetMaxChildIndexForScrollOffset
(
double
scrollOffset
,
double
itemExtent
)
{
...
...
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