Visitar URL original
Colored box and container optimization (#50979) · flutter/flutter@444b13b · GitHub
Skip to content

Commit 444b13b

Browse files
authored
Colored box and container optimization (#50979)
1 parent c75db98 commit 444b13b

11 files changed

Lines changed: 241 additions & 112 deletions

‎packages/flutter/lib/src/widgets/basic.dart‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7071,3 +7071,57 @@ class _StatefulBuilderState extends State<StatefulBuilder> {
70717071
@override
70727072
Widget build(BuildContext context) => widget.builder(context, setState);
70737073
}
7074+
7075+
/// A widget that paints its area with a specified [Color] and then draws its
7076+
/// child on top of that color.
7077+
class ColoredBox extends SingleChildRenderObjectWidget {
7078+
/// Creates a widget that paints its area with the specified [Color].
7079+
///
7080+
/// The [color] parameter must not be null.
7081+
const ColoredBox({@required this.color, Widget child, Key key})
7082+
: assert(color != null),
7083+
super(key: key, child: child);
7084+
7085+
/// The color to paint the background area with.
7086+
final Color color;
7087+
7088+
@override
7089+
_RenderColoredBox createRenderObject(BuildContext context) {
7090+
return _RenderColoredBox(color: color);
7091+
}
7092+
7093+
@override
7094+
void updateRenderObject(BuildContext context, _RenderColoredBox renderObject) {
7095+
renderObject.color = color;
7096+
}
7097+
}
7098+
7099+
class _RenderColoredBox extends RenderProxyBoxWithHitTestBehavior {
7100+
_RenderColoredBox({@required Color color})
7101+
: _color = color,
7102+
super(behavior: HitTestBehavior.opaque);
7103+
7104+
/// The fill color for this render object.
7105+
///
7106+
/// This parameter must not be null.
7107+
Color get color => _color;
7108+
Color _color;
7109+
set color(Color value) {
7110+
assert(value != null);
7111+
if (value == _color) {
7112+
return;
7113+
}
7114+
_color = value;
7115+
markNeedsPaint();
7116+
}
7117+
7118+
@override
7119+
void paint(PaintingContext context, Offset offset) {
7120+
if (size > Size.zero) {
7121+
context.canvas.drawRect(offset & size, Paint()..color = color);
7122+
}
7123+
if (child != null) {
7124+
context.paintChild(child, offset);
7125+
}
7126+
}
7127+
}

‎packages/flutter/lib/src/widgets/container.dart‎

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -294,17 +294,16 @@ class Container extends StatelessWidget {
294294
///
295295
/// The `height` and `width` values include the padding.
296296
///
297-
/// The `color` argument is a shorthand for `decoration: new
298-
/// BoxDecoration(color: color)`, which means you cannot supply both a `color`
299-
/// and a `decoration` argument. If you want to have both a `color` and a
300-
/// `decoration`, you can pass the color as the `color` argument to the
301-
/// `BoxDecoration`.
297+
/// The `color` and `decoration` arguments cannot both be supplied, since
298+
/// it would potentially result in the decoration drawing over the background
299+
/// color. To supply a decoration with a color, use `decoration:
300+
/// BoxDecoration(color: color)`.
302301
Container({
303302
Key key,
304303
this.alignment,
305304
this.padding,
306-
Color color,
307-
Decoration decoration,
305+
this.color,
306+
this.decoration,
308307
this.foregroundDecoration,
309308
double width,
310309
double height,
@@ -320,9 +319,8 @@ class Container extends StatelessWidget {
320319
assert(clipBehavior != null),
321320
assert(color == null || decoration == null,
322321
'Cannot provide both a color and a decoration\n'
323-
'The color argument is just a shorthand for "decoration: new BoxDecoration(color: color)".'
322+
'To provide both, use "decoration: BoxDecoration(color: color)".'
324323
),
325-
decoration = decoration ?? (color != null ? BoxDecoration(color: color) : null),
326324
constraints =
327325
(width != null || height != null)
328326
? constraints?.tighten(width: width, height: height)
@@ -363,11 +361,20 @@ class Container extends StatelessWidget {
363361
/// see [Decoration.padding].
364362
final EdgeInsetsGeometry padding;
365363

364+
/// The color to paint behind the [child].
365+
///
366+
/// This property should be preferred when the background is a simple color.
367+
/// For other cases, such as gradients or images, use the [decoration]
368+
/// property.
369+
///
370+
/// If the [decoration] is used, this property must be null. A background
371+
/// color may still be painted by the [decoration] even if this property is
372+
/// null.
373+
final Color color;
374+
366375
/// The decoration to paint behind the [child].
367376
///
368-
/// A shorthand for specifying just a solid color is available in the
369-
/// constructor: set the `color` argument instead of the `decoration`
370-
/// argument.
377+
/// Use the [color] property to specify a simple solid color.
371378
///
372379
/// The [child] is not clipped to the decoration. To clip a child to the shape
373380
/// of a particular [ShapeDecoration], consider using a [ClipPath] widget.
@@ -423,6 +430,9 @@ class Container extends StatelessWidget {
423430
if (effectivePadding != null)
424431
current = Padding(padding: effectivePadding, child: current);
425432

433+
if (color != null)
434+
current = ColoredBox(color: color, child: current);
435+
426436
if (decoration != null)
427437
current = DecoratedBox(decoration: decoration, child: current);
428438

‎packages/flutter/test/material/banner_test.dart‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ void main() {
2525
);
2626

2727
final Container container = _getContainerFromBanner(tester);
28-
expect(container.decoration, const BoxDecoration(color: color));
28+
expect(container.color, color);
2929
});
3030

3131
testWidgets('Custom content TextStyle respected', (WidgetTester tester) async {

‎packages/flutter/test/material/banner_theme_test.dart‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ void main() {
6666

6767
final Container container = _getContainerFromBanner(tester);
6868
final RenderParagraph content = _getTextRenderObjectFromDialog(tester, contentText);
69-
expect(container.decoration, const BoxDecoration(color: Color(0xffffffff)));
69+
expect(container.color, const Color(0xffffffff));
7070
// Default value for ThemeData.typography is Typography.material2014()
7171
expect(content.text.style, Typography.material2014().englishLike.bodyText2.merge(Typography.material2014().black.bodyText2));
7272
});
@@ -92,7 +92,7 @@ void main() {
9292

9393
final Container container = _getContainerFromBanner(tester);
9494
final RenderParagraph content = _getTextRenderObjectFromDialog(tester, contentText);
95-
expect(container.decoration, BoxDecoration(color: bannerTheme.backgroundColor));
95+
expect(container.color, bannerTheme.backgroundColor);
9696
expect(content.text.style, bannerTheme.contentTextStyle);
9797

9898
final Offset contentTopLeft = tester.getTopLeft(_textFinder(contentText));
@@ -131,7 +131,7 @@ void main() {
131131

132132
final Container container = _getContainerFromBanner(tester);
133133
final RenderParagraph content = _getTextRenderObjectFromDialog(tester, contentText);
134-
expect(container.decoration, const BoxDecoration(color: backgroundColor));
134+
expect(container.color, backgroundColor);
135135
expect(content.text.style, textStyle);
136136

137137
final Offset contentTopLeft = tester.getTopLeft(_textFinder(contentText));
@@ -161,7 +161,7 @@ void main() {
161161
));
162162

163163
final Container container = _getContainerFromBanner(tester);
164-
expect(container.decoration, BoxDecoration(color: colorScheme.surface));
164+
expect(container.color, colorScheme.surface);
165165
});
166166
}
167167

‎packages/flutter/test/material/drawer_test.dart‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,7 @@ void main() {
148148
scaffoldKey.currentState.openDrawer();
149149
await tester.pumpAndSettle();
150150

151-
BoxDecoration decoration = getScrim().decoration as BoxDecoration;
152-
expect(decoration.color, Colors.black54);
153-
expect(decoration.shape, BoxShape.rectangle);
151+
expect(getScrim().color, Colors.black54);
154152

155153
await tester.tap(find.byType(Drawer));
156154
await tester.pumpAndSettle();
@@ -162,9 +160,7 @@ void main() {
162160
scaffoldKey.currentState.openDrawer();
163161
await tester.pumpAndSettle();
164162

165-
decoration = getScrim().decoration as BoxDecoration;
166-
expect(decoration.color, const Color(0xFF323232));
167-
expect(decoration.shape, BoxShape.rectangle);
163+
expect(getScrim().color, const Color(0xFF323232));
168164

169165
await tester.tap(find.byType(Drawer));
170166
await tester.pumpAndSettle();

‎packages/flutter/test/material/inherited_theme_test.dart‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,7 @@ void main() {
7373
}
7474

7575
Color containerColor() {
76-
final BoxDecoration decoration = tester.widget<Container>(find.byKey(primaryContainerKey)).decoration as BoxDecoration;
77-
return decoration.color;
76+
return tester.widget<Container>(find.byKey(primaryContainerKey)).color;
7877
}
7978

8079
await tester.pumpWidget(buildFrame());
@@ -225,10 +224,9 @@ void main() {
225224
}
226225

227226
Color bannerColor() {
228-
final BoxDecoration decoration = tester.widget<Container>(
227+
return tester.widget<Container>(
229228
find.descendant(of: find.byType(MaterialBanner), matching: find.byType(Container)).first,
230-
).decoration as BoxDecoration;
231-
return decoration.color;
229+
).color;
232230
}
233231

234232
TextStyle getTextStyle(String text) {

‎packages/flutter/test/widgets/basic_test.dart‎

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:flutter_test/flutter_test.dart';
77
import 'package:flutter/material.dart';
88
import 'package:flutter/widgets.dart';
99
import 'package:flutter/rendering.dart';
10+
import 'package:mockito/mockito.dart';
1011

1112
void main() {
1213
group('PhysicalShape', () {
@@ -278,6 +279,90 @@ void main() {
278279
equals('UnconstrainedBox(alignment: topRight, constrainedAxis: horizontal, textDirection: rtl)'),
279280
);
280281
});
282+
283+
group('ColoredBox', () {
284+
_MockCanvas mockCanvas;
285+
_MockPaintingContext mockContext;
286+
const Color colorToPaint = Color(0xFFABCDEF);
287+
288+
setUp(() {
289+
mockContext = _MockPaintingContext();
290+
mockCanvas = _MockCanvas();
291+
when(mockContext.canvas).thenReturn(mockCanvas);
292+
});
293+
294+
testWidgets('ColoredBox - no size, no child', (WidgetTester tester) async {
295+
await tester.pumpWidget(Flex(
296+
direction: Axis.horizontal,
297+
textDirection: TextDirection.ltr,
298+
children: const <Widget>[
299+
SizedBox.shrink(
300+
child: ColoredBox(color: colorToPaint),
301+
),
302+
],
303+
));
304+
expect(find.byType(ColoredBox), findsOneWidget);
305+
final RenderObject renderColoredBox = tester.renderObject(find.byType(ColoredBox));
306+
307+
renderColoredBox.paint(mockContext, Offset.zero);
308+
309+
verifyNever(mockCanvas.drawRect(any, any));
310+
verifyNever(mockContext.paintChild(any, any));
311+
});
312+
313+
testWidgets('ColoredBox - no size, child', (WidgetTester tester) async {
314+
const ValueKey<int> key = ValueKey<int>(0);
315+
const Widget child = SizedBox.expand(key: key);
316+
await tester.pumpWidget(Flex(
317+
direction: Axis.horizontal,
318+
textDirection: TextDirection.ltr,
319+
children: const <Widget>[
320+
SizedBox.shrink(
321+
child: ColoredBox(color: colorToPaint, child: child),
322+
),
323+
],
324+
));
325+
expect(find.byType(ColoredBox), findsOneWidget);
326+
final RenderObject renderColoredBox = tester.renderObject(find.byType(ColoredBox));
327+
final RenderObject renderSizedBox = tester.renderObject(find.byKey(key));
328+
329+
renderColoredBox.paint(mockContext, Offset.zero);
330+
331+
verifyNever(mockCanvas.drawRect(any, any));
332+
verify(mockContext.paintChild(renderSizedBox, Offset.zero)).called(1);
333+
});
334+
335+
testWidgets('ColoredBox - size, no child', (WidgetTester tester) async {
336+
await tester.pumpWidget(const ColoredBox(color: colorToPaint));
337+
expect(find.byType(ColoredBox), findsOneWidget);
338+
final RenderObject renderColoredBox = tester.renderObject(find.byType(ColoredBox));
339+
340+
renderColoredBox.paint(mockContext, Offset.zero);
341+
342+
final List<dynamic> drawRect = verify(mockCanvas.drawRect(captureAny, captureAny)).captured;
343+
expect(drawRect.length, 2);
344+
expect(drawRect[0], const Rect.fromLTWH(0, 0, 800, 600));
345+
expect(drawRect[1].color, colorToPaint);
346+
verifyNever(mockContext.paintChild(any, any));
347+
});
348+
349+
testWidgets('ColoredBox - size, child', (WidgetTester tester) async {
350+
const ValueKey<int> key = ValueKey<int>(0);
351+
const Widget child = SizedBox.expand(key: key);
352+
await tester.pumpWidget(const ColoredBox(color: colorToPaint, child: child));
353+
expect(find.byType(ColoredBox), findsOneWidget);
354+
final RenderObject renderColoredBox = tester.renderObject(find.byType(ColoredBox));
355+
final RenderObject renderSizedBox = tester.renderObject(find.byKey(key));
356+
357+
renderColoredBox.paint(mockContext, Offset.zero);
358+
359+
final List<dynamic> drawRect = verify(mockCanvas.drawRect(captureAny, captureAny)).captured;
360+
expect(drawRect.length, 2);
361+
expect(drawRect[0], const Rect.fromLTWH(0, 0, 800, 600));
362+
expect(drawRect[1].color, colorToPaint);
363+
verify(mockContext.paintChild(renderSizedBox, Offset.zero)).called(1);
364+
});
365+
});
281366
}
282367

283368
HitsRenderBox hits(RenderBox renderBox) => HitsRenderBox(renderBox);
@@ -319,3 +404,6 @@ class DoesNotHitRenderBox extends Matcher {
319404
).isEmpty;
320405
}
321406
}
407+
408+
class _MockPaintingContext extends Mock implements PaintingContext {}
409+
class _MockCanvas extends Mock implements Canvas {}

0 commit comments

Comments
 (0)