Repository navigation
Colored box and container optimization - #50979
Conversation
|
Gold has detected one or more untriaged digests on patchset 2. |
| ], | ||
| )); | ||
| expect(find.byType(ColoredBox), findsOneWidget); | ||
| final RenderColoredBox renderColoredBox = tester.renderObject(find.byType(ColoredBox)) as RenderColoredBox; |
There was a problem hiding this comment.
tester.renderObject<RenderColoredBox>
|
|
||
| @override | ||
| void paint(PaintingContext context, Offset offset) { | ||
| if (size != Size.zero) { |
There was a problem hiding this comment.
I suppose to cover the corner of this corner case: (size > Size.zero)
| } | ||
|
|
||
| /// A render object that paints its area with the specified [Color]. | ||
| class RenderColoredBox extends RenderProxyBox { |
There was a problem hiding this comment.
Does this class need to be public?
There was a problem hiding this comment.
I suppose not. Making it private and will update the test.
|
This turns out to be a breaking change - we have tests in the framework that assert the right color is painted by looking for the BoxDecoration that a Container creates for the color. I think the right thing to do here is to add a matcher (which would fix #28753) and migrate tests to use that, in a separate PR. We can then migrate any internal customers to make that a soft break. |
|
Cirrus seems unhappy. |
| } | ||
|
|
||
| /// A widget that paints its area with a specified [Color] and then draws its | ||
| /// child. |
There was a problem hiding this comment.
maybe be specific and say that the child is drawn on top of that color?
There was a problem hiding this comment.
(even though that's kinda obvious, I guess..)
| class ColoredBox extends SingleChildRenderObjectWidget { | ||
| /// Creates a widget that paints its area with the specified [Color]. | ||
| /// | ||
| /// The `color` parameter must not be null. |
There was a problem hiding this comment.
color should be in [] since it exists as a property.
| /// `BoxDecoration`. | ||
| /// The `color` and `decoration` arguments cannot both be supplied, since | ||
| /// it would potentially result in the decoration drawing over the background | ||
| /// color. To supply a decoration with a color, use `decoration: new |
There was a problem hiding this comment.
drop the new here since it is no optional?
| class _RenderColoredBox extends RenderProxyBoxWithHitTestBehavior { | ||
| _RenderColoredBox({@required Color color}) | ||
| : _color = color, | ||
| super(behavior: color.alpha == 0 ? HitTestBehavior.deferToChild : HitTestBehavior.opaque); |
There was a problem hiding this comment.
This surprised me. Was the container doing anything special with hittest behavior based on transparency?
There was a problem hiding this comment.
I'm going to file a new bug for this: #51000 - agree we shouldn't introduce new behavior here (and not behavior that would differ from using a decoration)
| textDirection: TextDirection.ltr, | ||
| children: const <Widget>[ | ||
| SizedBox.shrink( | ||
| child: ColoredBox(color: colorToPaint, child: child), |
There was a problem hiding this comment.
Does the child = SizedBox.expand(key: key) also have 0 size? My first reaction is that a parent ColoredBox is at least the size of its child, so a 0-sized parent ColoredBox infers a 0-sized child.
There was a problem hiding this comment.
We have to break out of the constraints of the tester to make any of this work - we could also use a child instead of a parent to do it, but we need to make sure we're in a context where we won't just take up the full available space anwyya (e.g. using a flexible).
|
|
||
| final Container container = _getContainerFromBanner(tester); | ||
| expect(container.decoration, const BoxDecoration(color: color)); | ||
| expect(container.color, color); |
There was a problem hiding this comment.
Here and elsewhere: A simple container with a color no longer creates a BoxDecoration. This is the break.
There was a problem hiding this comment.
When making a change like this, are there metrics for before and after? I'm curious about rendering speed (which I would expect to improve) and app size (which I would expect to increase slightly).
There was a problem hiding this comment.
I recorded but have lost some performance gains in various timeline summary benchmarks we run. The results are not earth shaking, but it is a bump in performance for frame times - we use a lot of containers in the framework, and containers are a very common widget to use.
On the Flutter Gallery, building with this patch is not enough to meaningfully impact the app.so/app.dill sizes when building the release AOT bundle. They measure using the same number of blocks on my local disk with and without this patch.
There was a problem hiding this comment.
If I generate the heapsnapshots for each, the one with the ColoredBox takes slightly more space, indicating that the colored box is getting properly shaken out of both types.
The added classes are pretty tiny though :)
There was a problem hiding this comment.
Thanks for the info. Reading through this, I was suddenly curious what the life cycle of an SDK optimization like this was.
| itemExtent: 150, | ||
| delegate: SliverChildBuilderDelegate( | ||
| (BuildContext context, int index) => Container(color: Colors.amber), | ||
| (BuildContext context, int index) { |
There was a problem hiding this comment.
@Piinks - I've refactored this test to avoid checking the color(s) of the various slivers, and instead more explicitly check semantics labels corresponding to indexes.
This test would have failed because Container no longer creates a BoxDecoration in this case. Other approaches would include:
- Explicitly passing a decoration to the container we create in this test
- Using Keys - probably bad because it could affect ordering of the list, but this test doesn't seemto need that
- Refactor the test to use text widgets inside the slivers, which seemed more invasive.
There was a problem hiding this comment.
Sgtm! Thanks for updating the test!
| // Screen is 600px high. Moved bottom item 500px up. It's now at the top. | ||
| expect(tester.getTopLeft(find.widgetWithText(DecoratedBox, '5')).dy, 0.0); | ||
| expect(tester.getBottomLeft(find.widgetWithText(DecoratedBox, '10')).dy, 600.0); | ||
| expect(tester.getTopLeft(find.widgetWithText(Container, '5')).dy, 0.0); |
There was a problem hiding this comment.
This test was looking for an implementation detail of Container, no real need to, and that detail doesn't get created anymore in this case.
There was a problem hiding this comment.
In fairness, it was an evil test. 😄
|
Landing this will require updating tests in our own packages repo. |
|
I think the next step here is to work on a migration guide. I'll also try to run this through an internal test roll to see what the fallout is there. |
|
|
||
| /// The color to paint behind the [child]. | ||
| /// | ||
| /// This will paint behind the [decoration], if any. |
There was a problem hiding this comment.
Update this comment since you can no longer have both?
|
This should not land until cl/296911768 |
| assert(color == null || decoration == null, | ||
| 'Cannot provide both a color and a decoration\n' | ||
| 'The color argument is just a shorthand for "decoration: new BoxDecoration(color: color)".' | ||
| 'To provide both, use "decoration: new BoxDecoration(color: color)".' |
There was a problem hiding this comment.
nit: Don't need the new here?
| // Screen is 600px high. Moved bottom item 500px up. It's now at the top. | ||
| expect(tester.getTopLeft(find.widgetWithText(DecoratedBox, '5')).dy, 0.0); | ||
| expect(tester.getBottomLeft(find.widgetWithText(DecoratedBox, '10')).dy, 600.0); | ||
| expect(tester.getTopLeft(find.widgetWithText(Container, '5')).dy, 0.0); |
There was a problem hiding this comment.
In fairness, it was an evil test. 😄
|
|
||
| final Container container = _getContainerFromBanner(tester); | ||
| expect(container.decoration, const BoxDecoration(color: color)); | ||
| expect(container.color, color); |
There was a problem hiding this comment.
When making a change like this, are there metrics for before and after? I'm curious about rendering speed (which I would expect to improve) and app size (which I would expect to increase slightly).
|
Internal CL has landed. This is safe to land as a soft break at this point. |
* migration guide for flutter/flutter#50979 * review * revert unintended change * Update container-color.md
Description
It's fairly common to create a
Containerthat has just acolorproperty and no other decoration properties on it.We currently go down a slightly more expensive path than necessary to just draw a simple background color.
This PR adds a ColoredBox widget, and optimizes container to use that widget. It also lifts the restriction that
Containerspecify either color or decoration.Related Issues
Fixes #9672
Breaks suggestions in #28753
Tests
I added the following tests:
Tests that the new widget avoids drawing when its size is zero, and paints its child if it has one.
The complex_layout_scroll_perf test showed a slight improvement in everything but 99th percentile frame build and rasterizer times - although I strongly suspect that's mainly noise, and I'm also not running on a Moto G4. I anticipate a small improvement in several of our benchmarks due to this patch.
Checklist
Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes (
[x]). This will ensure a smooth and quick review process.///).flutter analyze --flutter-repo) does not report any problems on my PR.Breaking Change
Did any tests fail when you ran them? Please read Handling breaking changes.
Migration guide: flutter/website#3728