Unverified Commit 38065376 authored by liyuqian's avatar liyuqian Committed by GitHub

Skip painting hidden children of a sliver list (#18465)

We have many hidden children because of iOS accessibility (see also #18410).

For example, in the tile screen of complex_layout app, there could be up to 20 children being painted without this PR. With this PR, only 12 get painted.

This should improve the performance of FL-53 by 40%.
parent ac0aaf77
......@@ -524,7 +524,9 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
);
if (addExtent)
childOffset += mainAxisUnit * paintExtentOf(child);
context.paintChild(child, childOffset);
final double mainAxisOffset = mainAxisUnit.dx != 0 ? childOffset.dx : childOffset.dy;
if (mainAxisOffset < constraints.remainingPaintExtent && mainAxisOffset + paintExtentOf(child) > 0)
context.paintChild(child, childOffset);
child = childAfter(child);
}
}
......
......@@ -48,6 +48,11 @@ Matcher get paintsNothing => new _TestRecordingCanvasPaintsNothingMatcher();
/// Matches objects or functions that assert when they try to paint.
Matcher get paintsAssertion => new _TestRecordingCanvasPaintsAssertionMatcher();
/// Matches objects or functions that draw `methodName` exactly `count` number of times
Matcher paintsExactlyCountTimes(Symbol methodName, int count) {
return new _TestRecordingCanvasPaintsCountMatcher(methodName, count);
}
/// Signature for the [PaintPattern.something] and [PaintPattern.everything]
/// predicate argument.
///
......@@ -552,6 +557,34 @@ abstract class _TestRecordingCanvasMatcher extends Matcher {
}
}
class _TestRecordingCanvasPaintsCountMatcher extends _TestRecordingCanvasMatcher {
final Symbol _methodName;
final int _count;
_TestRecordingCanvasPaintsCountMatcher(Symbol methodName, int count)
: _methodName = methodName, _count = count;
@override
Description describe(Description description) {
return description.add('Object or closure painting $_methodName exactly $_count times');
}
@override
bool _evaluatePredicates(Iterable<RecordedInvocation> calls,
StringBuffer description) {
int count = 0;
for(RecordedInvocation call in calls) {
if (call.invocation.isMethod && call.invocation.memberName == _methodName) {
count++;
}
}
if (count != _count) {
description.write('It painted $_methodName $count times instead of $_count times.');
}
return count == _count;
}
}
class _TestRecordingCanvasPaintsNothingMatcher extends _TestRecordingCanvasMatcher {
@override
Description describe(Description description) {
......
......@@ -307,14 +307,14 @@ void main() {
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ size: Size(800.0, 400.0)\n'
' │\n'
' └─child with index 2: RenderLimitedBox#00000\n'
' └─child with index 2: RenderLimitedBox#00000 NEEDS-PAINT\n'
' │ parentData: index=2; layoutOffset=800.0\n'
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ size: Size(800.0, 400.0)\n'
' │ maxWidth: 400.0\n'
' │ maxHeight: 400.0\n'
' │\n'
' └─child: RenderCustomPaint#00000\n'
' └─child: RenderCustomPaint#00000 NEEDS-PAINT\n'
' parentData: <none> (can use size)\n'
' constraints: BoxConstraints(w=800.0, h=400.0)\n'
' size: Size(800.0, 400.0)\n'
......@@ -412,14 +412,14 @@ void main() {
' │ cacheExtent: 1100.0)\n'
' │ currently live children: 4 to 7\n'
' │\n'
' ├─child with index 4: RenderLimitedBox#00000\n'
' ├─child with index 4: RenderLimitedBox#00000 NEEDS-PAINT\n'
' │ │ parentData: index=4; layoutOffset=1600.0\n'
' │ │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ │ size: Size(800.0, 400.0)\n'
' │ │ maxWidth: 400.0\n'
' │ │ maxHeight: 400.0\n'
' │ │\n'
' │ └─child: RenderCustomPaint#00000\n'
' │ └─child: RenderCustomPaint#00000 NEEDS-PAINT\n'
' │ parentData: <none> (can use size)\n'
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ size: Size(800.0, 400.0)\n'
......@@ -448,14 +448,14 @@ void main() {
' │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' │ size: Size(800.0, 400.0)\n'
' │\n'
' ├─child with index 7: RenderLimitedBox#00000\n'
' ├─child with index 7: RenderLimitedBox#00000 NEEDS-PAINT\n'
' ╎ │ parentData: index=7; layoutOffset=2800.0\n'
' ╎ │ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' ╎ │ size: Size(800.0, 400.0)\n'
' ╎ │ maxWidth: 400.0\n'
' ╎ │ maxHeight: 400.0\n'
' ╎ │\n'
' ╎ └─child: RenderCustomPaint#00000\n'
' ╎ └─child: RenderCustomPaint#00000 NEEDS-PAINT\n'
' ╎ parentData: <none> (can use size)\n'
' ╎ constraints: BoxConstraints(w=800.0, h=400.0)\n'
' ╎ size: Size(800.0, 400.0)\n'
......
......@@ -6,6 +6,8 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import '../rendering/mock_canvas.dart';
import 'test_widgets.dart';
void main() {
......@@ -429,4 +431,33 @@ void main() {
expect(position.viewportDimension, equals(600.0));
expect(position.minScrollExtent, equals(0.0));
});
testWidgets('ListView should not paint hidden children', (WidgetTester tester) async {
const Text text = const Text('test');
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Center(
child: new Container(
height: 200.0,
child: new ListView(
cacheExtent: 500.0,
controller: new ScrollController(initialScrollOffset: 300.0),
children: <Widget>[
new Container(height: 140.0, child: text),
new Container(height: 160.0, child: text),
new Container(height: 90.0, child: text),
new Container(height: 110.0, child: text),
new Container(height: 80.0, child: text),
new Container(height: 70.0, child: text),
],
)
),
)
)
);
final RenderSliverList list = tester.renderObject(find.byType(SliverList));
expect(list, paintsExactlyCountTimes(#drawParagraph, 2));
});
}
......@@ -82,7 +82,7 @@ void main() {
' │ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ │ layer: OffsetLayer#00000\n'
' │ │ size: Size(800.0, 600.0)\n'
' │ │ metrics: 75.0% useful (1 bad vs 3 good)\n'
' │ │ metrics: 66.7% useful (1 bad vs 2 good)\n'
' │ │ diagnosis: insufficient data to draw conclusion (less than five\n'
' │ │ repaints)\n'
' │ │\n'
......@@ -103,9 +103,9 @@ void main() {
' └─child with index 1: RenderRepaintBoundary#00000\n'
' │ parentData: index=1; layoutOffset=600.0\n'
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ layer: OffsetLayer#00000\n'
' │ layer: OffsetLayer#00000 DETACHED\n'
' │ size: Size(800.0, 600.0)\n'
' │ metrics: 75.0% useful (1 bad vs 3 good)\n'
' │ metrics: 50.0% useful (1 bad vs 1 good)\n'
' │ diagnosis: insufficient data to draw conclusion (less than five\n'
' │ repaints)\n'
' │\n'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment