Optimize SliverMainAxisGroup/SliverCrossAxisGroup paint function (#129310)
This PR changes the paint functions for SliverMainAxisGroup and SliverCrossAxisGroup so that only visible slivers are painted. Fixes https://github.com/flutter/flutter/issues/129214.
This commit is contained in:
parent
c65cab8fa3
commit
8a37b8ba35
@ -129,8 +129,10 @@ class RenderSliverCrossAxisGroup extends RenderSliver with ContainerRenderObject
|
||||
RenderSliver? child = firstChild;
|
||||
|
||||
while (child != null) {
|
||||
final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData;
|
||||
context.paintChild(child, offset + childParentData.paintOffset);
|
||||
if (child.geometry!.visible) {
|
||||
final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData;
|
||||
context.paintChild(child, offset + childParentData.paintOffset);
|
||||
}
|
||||
child = childAfter(child);
|
||||
}
|
||||
}
|
||||
@ -294,8 +296,10 @@ class RenderSliverMainAxisGroup extends RenderSliver with ContainerRenderObjectM
|
||||
RenderSliver? child = lastChild;
|
||||
|
||||
while (child != null) {
|
||||
final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData;
|
||||
context.paintChild(child, offset + childParentData.paintOffset);
|
||||
if (child.geometry!.visible) {
|
||||
final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData;
|
||||
context.paintChild(child, offset + childParentData.paintOffset);
|
||||
}
|
||||
child = childBefore(child);
|
||||
}
|
||||
}
|
||||
|
36
packages/flutter/test/rendering/sliver_utils.dart
Normal file
36
packages/flutter/test/rendering/sliver_utils.dart
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Test sliver which always attempts to paint itself whether it is visible or not.
|
||||
// Use for checking if slivers which take sliver children paints optimally.
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
class RenderMockSliverToBoxAdapter extends RenderSliverToBoxAdapter {
|
||||
RenderMockSliverToBoxAdapter({
|
||||
super.child,
|
||||
required this.incrementCounter,
|
||||
});
|
||||
final void Function() incrementCounter;
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
incrementCounter();
|
||||
}
|
||||
}
|
||||
|
||||
class MockSliverToBoxAdapter extends SingleChildRenderObjectWidget {
|
||||
/// Creates a sliver that contains a single box widget.
|
||||
const MockSliverToBoxAdapter({
|
||||
super.key,
|
||||
super.child,
|
||||
required this.incrementCounter,
|
||||
});
|
||||
|
||||
final void Function() incrementCounter;
|
||||
|
||||
@override
|
||||
RenderMockSliverToBoxAdapter createRenderObject(BuildContext context) =>
|
||||
RenderMockSliverToBoxAdapter(incrementCounter: incrementCounter);
|
||||
}
|
@ -6,6 +6,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../rendering/sliver_utils.dart';
|
||||
|
||||
|
||||
const double VIEWPORT_HEIGHT = 600;
|
||||
const double VIEWPORT_WIDTH = 300;
|
||||
|
||||
@ -806,8 +809,63 @@ void main() {
|
||||
// If renderHeader._lastStartedScrollDirection is not ScrollDirection.forward, then we shouldn't see the header at all.
|
||||
expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(0.0));
|
||||
});
|
||||
|
||||
testWidgets('SliverCrossAxisGroup skips painting invisible children', (WidgetTester tester) async {
|
||||
final ScrollController controller = ScrollController();
|
||||
|
||||
int counter = 0;
|
||||
void incrementCounter() {
|
||||
counter += 1;
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
_buildSliverCrossAxisGroup(
|
||||
controller: controller,
|
||||
slivers: <Widget>[
|
||||
MockSliverToBoxAdapter(
|
||||
incrementCounter: incrementCounter,
|
||||
child: Container(
|
||||
height: 1000,
|
||||
decoration: const BoxDecoration(color: Colors.amber),
|
||||
),
|
||||
),
|
||||
MockSliverToBoxAdapter(
|
||||
incrementCounter: incrementCounter,
|
||||
child: Container(
|
||||
height: 400,
|
||||
decoration: const BoxDecoration(color: Colors.amber)
|
||||
),
|
||||
),
|
||||
MockSliverToBoxAdapter(
|
||||
incrementCounter: incrementCounter,
|
||||
child: Container(
|
||||
height: 500,
|
||||
decoration: const BoxDecoration(color: Colors.amber)
|
||||
),
|
||||
),
|
||||
MockSliverToBoxAdapter(
|
||||
incrementCounter: incrementCounter,
|
||||
child: Container(
|
||||
height: 300,
|
||||
decoration: const BoxDecoration(color: Colors.amber)
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
expect(counter, equals(4));
|
||||
|
||||
// Reset paint counter.
|
||||
counter = 0;
|
||||
controller.jumpTo(400);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(controller.offset, 400);
|
||||
expect(counter, equals(2));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Widget _buildSliverList({
|
||||
double itemMainAxisExtent = 100,
|
||||
List<int> items = const <int>[],
|
||||
|
@ -6,6 +6,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../rendering/sliver_utils.dart';
|
||||
|
||||
|
||||
const double VIEWPORT_HEIGHT = 600;
|
||||
const double VIEWPORT_WIDTH = 300;
|
||||
|
||||
@ -604,6 +607,63 @@ void main() {
|
||||
expect(renderHeader.geometry!.paintExtent, equals(60.0));
|
||||
expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(-50.0));
|
||||
});
|
||||
|
||||
testWidgets('SliverMainAxisGroup skips painting invisible children', (WidgetTester tester) async {
|
||||
final ScrollController controller = ScrollController();
|
||||
|
||||
int counter = 0;
|
||||
void incrementCounter() {
|
||||
counter += 1;
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
_buildSliverMainAxisGroup(
|
||||
controller: controller,
|
||||
slivers: <Widget>[
|
||||
MockSliverToBoxAdapter(
|
||||
incrementCounter: incrementCounter,
|
||||
child: Container(
|
||||
height: 1000,
|
||||
decoration: const BoxDecoration(color: Colors.amber),
|
||||
),
|
||||
),
|
||||
MockSliverToBoxAdapter(
|
||||
incrementCounter: incrementCounter,
|
||||
child: Container(
|
||||
height: 400,
|
||||
decoration: const BoxDecoration(color: Colors.amber)
|
||||
),
|
||||
),
|
||||
MockSliverToBoxAdapter(
|
||||
incrementCounter: incrementCounter,
|
||||
child: Container(
|
||||
height: 500,
|
||||
decoration: const BoxDecoration(color: Colors.amber)
|
||||
),
|
||||
),
|
||||
MockSliverToBoxAdapter(
|
||||
incrementCounter: incrementCounter,
|
||||
child: Container(
|
||||
height: 300,
|
||||
decoration: const BoxDecoration(color: Colors.amber)
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
// Can only see top sliver.
|
||||
expect(counter, equals(1));
|
||||
|
||||
// Reset paint counter.
|
||||
counter = 0;
|
||||
controller.jumpTo(1000);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Can only see second and third slivers.
|
||||
expect(controller.offset, 1000);
|
||||
expect(counter, equals(2));
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildSliverList({
|
||||
|
Loading…
x
Reference in New Issue
Block a user