Skip to content

Colored box and container optimization - #50979

Merged
dnfield merged 16 commits into
flutter:masterfrom
dnfield:colored_box
Feb 27, 2020
Merged

dnfield merged 16 commits into
flutter:masterfrom
dnfield:colored_box

Conversation

@dnfield

@dnfield dnfield commented Feb 18, 2020 •

Copy link
Copy Markdown
Contributor

Description

It's fairly common to create a Container that has just a color property 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 Container specify 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.

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I signed the CLA.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I updated/added relevant documentation (doc comments with ///).
  • All existing and new tests are passing.
  • The analyzer (flutter analyze --flutter-repo) does not report any problems on my PR.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

Did any tests fail when you ran them? Please read Handling breaking changes.

  • Yes, this is a breaking change.

Migration guide: flutter/website#3728

@fluttergithubbot fluttergithubbot added the framework flutter/packages/flutter repository. See also f: labels. label Feb 18, 2020
@skia-gold

Copy link
Copy Markdown

Gold has detected one or more untriaged digests on patchset 2.
Please triage them at https://flutter-gold.skia.org/search?issue=50979.

],
));
expect(find.byType(ColoredBox), findsOneWidget);
final RenderColoredBox renderColoredBox = tester.renderObject(find.byType(ColoredBox)) as RenderColoredBox;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tester.renderObject<RenderColoredBox>


@override
void paint(PaintingContext context, Offset offset) {
if (size != Size.zero) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose to cover the corner of this corner case: (size > Size.zero)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

/// A render object that paints its area with the specified [Color].
class RenderColoredBox extends RenderProxyBox {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this class need to be public?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose not. Making it private and will update the test.

@dnfield

dnfield commented Feb 18, 2020

Copy link
Copy Markdown
Contributor Author

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.

@goderbauer

Copy link
Copy Markdown
Member

Cirrus seems unhappy.

}

/// A widget that paints its area with a specified [Color] and then draws its
/// child.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe be specific and say that the child is drawn on top of that color?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(even though that's kinda obvious, I guess..)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

class ColoredBox extends SingleChildRenderObjectWidget {
/// Creates a widget that paints its area with the specified [Color].
///
/// The `color` parameter must not be null.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

color should be in [] since it exists as a property.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

/// `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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drop the new here since it is no optional?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

class _RenderColoredBox extends RenderProxyBoxWithHitTestBehavior {
_RenderColoredBox({@required Color color})
: _color = color,
super(behavior: color.alpha == 0 ? HitTestBehavior.deferToChild : HitTestBehavior.opaque);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This surprised me. Was the container doing anything special with hittest behavior based on transparency?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

@fluttergithubbot fluttergithubbot added the p: material_ui material_ui package in flutter/packages label Feb 19, 2020

final Container container = _getContainerFromBanner(tester);
expect(container.decoration, const BoxDecoration(color: color));
expect(container.color, color);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and elsewhere: A simple container with a color no longer creates a BoxDecoration. This is the break.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fairness, it was an evil test. 😄

@dnfield

dnfield commented Feb 19, 2020

Copy link
Copy Markdown
Contributor Author

Landing this will require updating tests in our own packages repo.

dnfield added a commit to flutter/tests that referenced this pull request Feb 19, 2020
@Piinks Piinks added c: API break Backwards-incompatible API changes a: tests "flutter test", flutter_test, or one of our tests labels Feb 19, 2020
@Piinks Piinks added perf: speed Performance issues related to (mostly rendering) speed c: performance Relates to speed or footprint issues (see "perf:" labels) labels Feb 19, 2020
dnfield added a commit to flutter/tests that referenced this pull request Feb 19, 2020
@dnfield

dnfield commented Feb 20, 2020

Copy link
Copy Markdown
Contributor Author

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.

@goderbauer goderbauer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM


/// The color to paint behind the [child].
///
/// This will paint behind the [decoration], if any.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update this comment since you can no longer have both?

@dnfield

dnfield commented Feb 25, 2020

Copy link
Copy Markdown
Contributor Author

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)".'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Don't need the new here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

// 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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fairness, it was an evil test. 😄


final Container container = _getContainerFromBanner(tester);
expect(container.decoration, const BoxDecoration(color: color));
expect(container.color, color);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

@dnfield

dnfield commented Feb 25, 2020

Copy link
Copy Markdown
Contributor Author

Internal CL has landed. This is safe to land as a soft break at this point.

@dnfield
dnfield merged commit 444b13b into flutter:master Feb 27, 2020
@dnfield
dnfield deleted the colored_box branch February 27, 2020 00:04
sfshaza2 added a commit to flutter/website that referenced this pull request Mar 6, 2020
* migration guide for flutter/flutter#50979

* review

* revert unintended change

* Update container-color.md
@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Aug 1, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

a: tests "flutter test", flutter_test, or one of our tests c: API break Backwards-incompatible API changes c: performance Relates to speed or footprint issues (see "perf:" labels) framework flutter/packages/flutter repository. See also f: labels. p: material_ui material_ui package in flutter/packages perf: speed Performance issues related to (mostly rendering) speed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Optimise Container(color: ...)

9 participants