mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
This PR fixed visual bug when placing multiple `ColoredBox`s together. (Fixes #176028) ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Victor Sanni <victorsanniay@gmail.com>
This commit is contained in:
parent
02d6e8fff6
commit
7efa2aed3c
@ -8400,31 +8400,59 @@ class _StatefulBuilderState extends State<StatefulBuilder> {
|
||||
/// child on top of that color.
|
||||
class ColoredBox extends SingleChildRenderObjectWidget {
|
||||
/// Creates a widget that paints its area with the specified [Color].
|
||||
const ColoredBox({required this.color, super.child, super.key});
|
||||
const ColoredBox({required this.color, this.isAntiAlias = true, super.child, super.key});
|
||||
|
||||
/// The color to paint the background area with.
|
||||
final Color color;
|
||||
|
||||
/// {@template flutter.widgets.ColoredBox.isAntiAlias}
|
||||
/// Whether to apply anti-aliasing when painting the box.
|
||||
///
|
||||
/// Defaults to `true`.
|
||||
///
|
||||
/// When `true`, the painted box will have smooth edges. This is crucial for
|
||||
/// animations and transformations (such as rotation or scaling) where the
|
||||
/// widget's edges may not align perfectly with the physical pixel grid.
|
||||
/// Anti-aliasing allows for sub-pixel rendering, which prevents a 'jagged'
|
||||
/// appearance during motion and ensures visually smooth transitions.
|
||||
///
|
||||
/// Set this to `false` for specific use cases where multiple `ColoredBox`
|
||||
/// widgets are positioned adjacent to each other to form a larger, seamless
|
||||
/// area of solid color. With anti-aliasing enabled (`true`), faint seams or
|
||||
/// gaps might appear between the boxes due to the semi-transparent pixels at
|
||||
/// their edges. Disabling anti-aliasing ensures that the boxes align perfectly
|
||||
/// without such visual artifacts.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Paint.isAntiAlias], the underlying property that this controls.
|
||||
/// {@endtemplate}
|
||||
final bool isAntiAlias;
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
return _RenderColoredBox(color: color);
|
||||
return _RenderColoredBox(color: color, isAntiAlias: isAntiAlias);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, RenderObject renderObject) {
|
||||
(renderObject as _RenderColoredBox).color = color;
|
||||
(renderObject as _RenderColoredBox)
|
||||
..color = color
|
||||
..isAntiAlias = isAntiAlias;
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<Color>('color', color));
|
||||
properties.add(DiagnosticsProperty<bool>('isAntiAlias', isAntiAlias, defaultValue: true));
|
||||
}
|
||||
}
|
||||
|
||||
class _RenderColoredBox extends RenderProxyBoxWithHitTestBehavior {
|
||||
_RenderColoredBox({required Color color})
|
||||
_RenderColoredBox({required Color color, required bool isAntiAlias})
|
||||
: _color = color,
|
||||
_isAntiAlias = isAntiAlias,
|
||||
super(behavior: HitTestBehavior.opaque);
|
||||
|
||||
/// The fill color for this render object.
|
||||
@ -8438,6 +8466,16 @@ class _RenderColoredBox extends RenderProxyBoxWithHitTestBehavior {
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
bool get isAntiAlias => _isAntiAlias;
|
||||
bool _isAntiAlias;
|
||||
set isAntiAlias(bool value) {
|
||||
if (value == _isAntiAlias) {
|
||||
return;
|
||||
}
|
||||
_isAntiAlias = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
// It's tempting to want to optimize out this `drawRect()` call if the
|
||||
@ -8445,7 +8483,12 @@ class _RenderColoredBox extends RenderProxyBoxWithHitTestBehavior {
|
||||
// https://github.com/flutter/flutter/pull/72526#issuecomment-749185938 for
|
||||
// a good description of why.
|
||||
if (size > Size.zero) {
|
||||
context.canvas.drawRect(offset & size, Paint()..color = color);
|
||||
context.canvas.drawRect(
|
||||
offset & size,
|
||||
Paint()
|
||||
..isAntiAlias = isAntiAlias
|
||||
..color = color,
|
||||
);
|
||||
}
|
||||
if (child != null) {
|
||||
context.paintChild(child!, offset);
|
||||
|
||||
@ -257,6 +257,7 @@ class Container extends StatelessWidget {
|
||||
this.alignment,
|
||||
this.padding,
|
||||
this.color,
|
||||
this.isAntiAlias = true,
|
||||
this.decoration,
|
||||
this.foregroundDecoration,
|
||||
double? width,
|
||||
@ -326,6 +327,9 @@ class Container extends StatelessWidget {
|
||||
/// null.
|
||||
final Color? color;
|
||||
|
||||
/// {@macro flutter.widgets.ColoredBox.isAntiAlias}
|
||||
final bool isAntiAlias;
|
||||
|
||||
/// The decoration to paint behind the [child].
|
||||
///
|
||||
/// Use the [color] property to specify a simple solid color.
|
||||
@ -398,7 +402,7 @@ class Container extends StatelessWidget {
|
||||
}
|
||||
|
||||
if (color != null) {
|
||||
current = ColoredBox(color: color!, child: current);
|
||||
current = ColoredBox(color: color!, isAntiAlias: isAntiAlias, child: current);
|
||||
}
|
||||
|
||||
if (clipBehavior != Clip.none) {
|
||||
|
||||
@ -1220,6 +1220,214 @@ void main() {
|
||||
|
||||
expect(properties.properties.first.value, colorToPaint);
|
||||
});
|
||||
|
||||
testWidgets('ColoredBox - default isAntiAlias', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(const ColoredBox(color: colorToPaint));
|
||||
expect(find.byType(ColoredBox), findsOneWidget);
|
||||
final RenderObject renderColoredBox = tester.renderObject(find.byType(ColoredBox));
|
||||
|
||||
renderColoredBox.paint(mockContext, Offset.zero);
|
||||
expect(mockCanvas.paints.single.isAntiAlias, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('ColoredBox - passing isAntiAlias = false', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(const ColoredBox(color: colorToPaint, isAntiAlias: false));
|
||||
expect(find.byType(ColoredBox), findsOneWidget);
|
||||
final RenderObject renderColoredBox = tester.renderObject(find.byType(ColoredBox));
|
||||
|
||||
renderColoredBox.paint(mockContext, Offset.zero);
|
||||
expect(mockCanvas.paints.single.isAntiAlias, isFalse);
|
||||
});
|
||||
|
||||
// This test verifies how `ColoredBox.isAntiAlias` affects rendering.
|
||||
// The first row uses `isAntiAlias: true`, showing gaps between the white backgrounds.
|
||||
// The second row uses `isAntiAlias: false`, demonstrating no gaps between the white backgrounds.
|
||||
// The third row contains three tilted boxes with `isAntiAlias` set to true, false, and false, respectively.
|
||||
testWidgets('ColoredBox golden test - anti-aliasing and rotation variations', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
Center(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: RepaintBoundary(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 8,
|
||||
children: <Widget>[
|
||||
// Intentionally 4% larger than the original size to test anti-aliasing
|
||||
Transform.scale(
|
||||
scale: 1.04,
|
||||
child: const ColoredBox(
|
||||
color: Colors.orange,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(2),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
ColoredBox(
|
||||
color: Colors.white,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(4.0),
|
||||
child: Text(
|
||||
'Short',
|
||||
style: TextStyle(fontSize: 16, color: Colors.black),
|
||||
),
|
||||
),
|
||||
),
|
||||
ColoredBox(
|
||||
color: Colors.white,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(4.0),
|
||||
child: Text(
|
||||
'Just text ',
|
||||
style: TextStyle(fontSize: 14, color: Colors.black),
|
||||
),
|
||||
),
|
||||
),
|
||||
ColoredBox(
|
||||
color: Colors.white,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(4.0),
|
||||
child: Text(
|
||||
' Tall text ',
|
||||
style: TextStyle(fontSize: 18, color: Colors.black),
|
||||
),
|
||||
),
|
||||
),
|
||||
ColoredBox(
|
||||
color: Colors.white,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(4.0),
|
||||
child: Text(
|
||||
'Medium',
|
||||
style: TextStyle(fontSize: 32, color: Colors.black),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Transform.scale(
|
||||
scale: 1.04,
|
||||
child: const ColoredBox(
|
||||
color: Colors.orange,
|
||||
isAntiAlias: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(2),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
ColoredBox(
|
||||
color: Colors.white,
|
||||
isAntiAlias: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(4.0),
|
||||
child: Text(
|
||||
'Short',
|
||||
style: TextStyle(fontSize: 16, color: Colors.black),
|
||||
),
|
||||
),
|
||||
),
|
||||
ColoredBox(
|
||||
color: Colors.white,
|
||||
isAntiAlias: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(4.0),
|
||||
child: Text(
|
||||
'Just text ',
|
||||
style: TextStyle(fontSize: 14, color: Colors.black),
|
||||
),
|
||||
),
|
||||
),
|
||||
ColoredBox(
|
||||
color: Colors.white,
|
||||
isAntiAlias: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(4.0),
|
||||
child: Text(
|
||||
' Tall text ',
|
||||
style: TextStyle(fontSize: 18, color: Colors.black),
|
||||
),
|
||||
),
|
||||
),
|
||||
ColoredBox(
|
||||
color: Colors.white,
|
||||
isAntiAlias: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(4.0),
|
||||
child: Text(
|
||||
'Medium',
|
||||
style: TextStyle(fontSize: 32, color: Colors.black),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
SizedBox.square(
|
||||
dimension: 80,
|
||||
child: Center(
|
||||
child: SizedBox.square(
|
||||
dimension: 50,
|
||||
child: Transform.rotate(
|
||||
angle: math.pi / 5,
|
||||
child: const ColoredBox(color: Colors.blue),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox.square(
|
||||
dimension: 80,
|
||||
child: Center(
|
||||
child: SizedBox.square(
|
||||
dimension: 50,
|
||||
child: Transform.rotate(
|
||||
angle: math.pi / 5,
|
||||
child: const ColoredBox(color: Colors.amber, isAntiAlias: false),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox.square(
|
||||
dimension: 80,
|
||||
child: Center(
|
||||
child: SizedBox.square(
|
||||
dimension: 50,
|
||||
child: Transform.rotate(
|
||||
angle: math.pi / 5,
|
||||
child: Transform.scale(
|
||||
scale: 1.2,
|
||||
child: const ColoredBox(color: Colors.teal, isAntiAlias: false),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
await expectLater(find.byType(RepaintBoundary), matchesGoldenFile('basic.ColoredBox.0.png'));
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('Inconsequential golden test', (WidgetTester tester) async {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user