Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 75ca31b

Browse files
authored
Correct Badge interpretation of its alignment parameter (#119853)
1 parent d8154fd commit 75ca31b

File tree

5 files changed

+287
-50
lines changed

5 files changed

+287
-50
lines changed

dev/tools/gen_defaults/lib/badge_template.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class _${blockName}DefaultsM3 extends BadgeThemeData {
1616
smallSize: ${tokens["md.comp.badge.size"]},
1717
largeSize: ${tokens["md.comp.badge.large.size"]},
1818
padding: const EdgeInsets.symmetric(horizontal: 4),
19-
alignment: const AlignmentDirectional(12, -4),
19+
alignment: AlignmentDirectional.topEnd,
2020
);
2121
2222
final BuildContext context;

packages/flutter/lib/src/material/badge.dart

Lines changed: 106 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'package:flutter/rendering.dart';
56
import 'package:flutter/widgets.dart';
67

78
import 'badge_theme.dart';
@@ -35,6 +36,7 @@ class Badge extends StatelessWidget {
3536
this.textStyle,
3637
this.padding,
3738
this.alignment,
39+
this.offset,
3840
this.label,
3941
this.isLabelVisible = true,
4042
this.child,
@@ -54,6 +56,7 @@ class Badge extends StatelessWidget {
5456
this.textStyle,
5557
this.padding,
5658
this.alignment,
59+
this.offset,
5760
required int count,
5861
this.isLabelVisible = true,
5962
this.child,
@@ -106,13 +109,29 @@ class Badge extends StatelessWidget {
106109
/// left and right if the theme's value is null.
107110
final EdgeInsetsGeometry? padding;
108111

109-
/// The location of the [label] relative to the [child].
112+
/// Combined with [offset] to determine the location of the [label]
113+
/// relative to the [child].
114+
///
115+
/// The alignment positions the label in the same way a child of an
116+
/// [Align] widget is positioned, except that, the alignment is
117+
/// resolved as if the label was a [largeSize] square and [offset]
118+
/// is added to the result.
119+
///
120+
/// This value is only used if [label] is non-null.
121+
///
122+
/// Defaults to the [BadgeTheme]'s alignment, or
123+
/// [AlignmentDirectional.topEnd] if the theme's value is null.
124+
final AlignmentGeometry? alignment;
125+
126+
/// Combined with [alignment] to determine the location of the [label]
127+
/// relative to the [child].
110128
///
111129
/// This value is only used if [label] is non-null.
112130
///
113-
/// Defaults to the [BadgeTheme]'s alignment, or `start = 12`
114-
/// and `top = -4` if the theme's value is null.
115-
final AlignmentDirectional? alignment;
131+
/// Defaults to the [BadgeTheme]'s offset, or
132+
/// if the theme's value is null then `Offset(4, -4)` for
133+
/// [TextDirection.ltr] or `Offset(-4, -4)` for [TextDirection.rtl].
134+
final Offset? offset;
116135

117136
/// The badge's content, typically a [Text] widget that contains 1 to 4
118137
/// characters.
@@ -168,24 +187,99 @@ class Badge extends StatelessWidget {
168187
return badge;
169188
}
170189

171-
final AlignmentDirectional effectiveAlignment = alignment ?? badgeTheme.alignment ?? defaults.alignment!;
190+
final AlignmentGeometry effectiveAlignment = alignment ?? badgeTheme.alignment ?? defaults.alignment!;
191+
final TextDirection textDirection = Directionality.of(context);
192+
final Offset defaultOffset = textDirection == TextDirection.ltr ? const Offset(4, -4) : const Offset(-4, -4);
193+
final Offset effectiveOffset = offset ?? badgeTheme.offset ?? defaultOffset;
194+
172195
return
173196
Stack(
174197
clipBehavior: Clip.none,
175198
children: <Widget>[
176199
child!,
177-
Positioned.directional(
178-
textDirection: Directionality.of(context),
179-
start: label == null ? null : effectiveAlignment.start,
180-
end: label == null ? 0 : null,
181-
top: label == null ? 0 : effectiveAlignment.y,
182-
child: badge,
200+
Positioned.fill(
201+
child: _Badge(
202+
alignment: effectiveAlignment,
203+
offset: label == null ? Offset.zero : effectiveOffset,
204+
textDirection: textDirection,
205+
child: badge,
206+
),
183207
),
184208
],
185209
);
186210
}
187211
}
188212

213+
class _Badge extends SingleChildRenderObjectWidget {
214+
const _Badge({
215+
required this.alignment,
216+
required this.offset,
217+
required this.textDirection,
218+
super.child, // the badge
219+
});
220+
221+
final AlignmentGeometry alignment;
222+
final Offset offset;
223+
final TextDirection textDirection;
224+
225+
@override
226+
_RenderBadge createRenderObject(BuildContext context) {
227+
return _RenderBadge(
228+
alignment: alignment,
229+
offset: offset,
230+
textDirection: Directionality.maybeOf(context),
231+
);
232+
}
233+
234+
@override
235+
void updateRenderObject(BuildContext context, _RenderBadge renderObject) {
236+
renderObject
237+
..alignment = alignment
238+
..offset = offset
239+
..textDirection = Directionality.maybeOf(context);
240+
}
241+
242+
@override
243+
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
244+
super.debugFillProperties(properties);
245+
properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
246+
properties.add(DiagnosticsProperty<Offset>('offset', offset));
247+
}
248+
}
249+
250+
class _RenderBadge extends RenderAligningShiftedBox {
251+
_RenderBadge({
252+
super.textDirection,
253+
super.alignment,
254+
required Offset offset,
255+
}) : _offset = offset;
256+
257+
Offset get offset => _offset;
258+
Offset _offset;
259+
set offset(Offset value) {
260+
if (_offset == value) {
261+
return;
262+
}
263+
_offset = value;
264+
markNeedsLayout();
265+
}
266+
267+
@override
268+
void performLayout() {
269+
final BoxConstraints constraints = this.constraints;
270+
assert(constraints.hasBoundedWidth);
271+
assert(constraints.hasBoundedHeight);
272+
size = constraints.biggest;
273+
274+
child!.layout(const BoxConstraints(), parentUsesSize: true);
275+
final double badgeSize = child!.size.height;
276+
final Alignment resolvedAlignment = alignment.resolve(textDirection);
277+
final BoxParentData childParentData = child!.parentData! as BoxParentData;
278+
childParentData.offset = offset + resolvedAlignment.alongOffset(Offset(size.width - badgeSize, size.height - badgeSize));
279+
}
280+
}
281+
282+
189283
// BEGIN GENERATED TOKEN PROPERTIES - Badge
190284

191285
// Do not edit by hand. The code between the "BEGIN GENERATED" and
@@ -200,7 +294,7 @@ class _BadgeDefaultsM3 extends BadgeThemeData {
200294
smallSize: 6.0,
201295
largeSize: 16.0,
202296
padding: const EdgeInsets.symmetric(horizontal: 4),
203-
alignment: const AlignmentDirectional(12, -4),
297+
alignment: AlignmentDirectional.topEnd,
204298
);
205299

206300
final BuildContext context;

packages/flutter/lib/src/material/badge_theme.dart

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class BadgeThemeData with Diagnosticable {
4141
this.textStyle,
4242
this.padding,
4343
this.alignment,
44+
this.offset,
4445
});
4546

4647
/// Overrides the default value for [Badge.backgroundColor].
@@ -62,7 +63,10 @@ class BadgeThemeData with Diagnosticable {
6263
final EdgeInsetsGeometry? padding;
6364

6465
/// Overrides the default value for [Badge.alignment].
65-
final AlignmentDirectional? alignment;
66+
final AlignmentGeometry? alignment;
67+
68+
/// Overrides the default value for [Badge.offset].
69+
final Offset? offset;
6670

6771
/// Creates a copy of this object but with the given fields replaced with the
6872
/// new values.
@@ -73,7 +77,8 @@ class BadgeThemeData with Diagnosticable {
7377
double? largeSize,
7478
TextStyle? textStyle,
7579
EdgeInsetsGeometry? padding,
76-
AlignmentDirectional? alignment,
80+
AlignmentGeometry? alignment,
81+
Offset? offset,
7782
}) {
7883
return BadgeThemeData(
7984
backgroundColor: backgroundColor ?? this.backgroundColor,
@@ -83,6 +88,7 @@ class BadgeThemeData with Diagnosticable {
8388
textStyle: textStyle ?? this.textStyle,
8489
padding: padding ?? this.padding,
8590
alignment: alignment ?? this.alignment,
91+
offset: offset ?? this.offset,
8692
);
8793
}
8894

@@ -95,7 +101,8 @@ class BadgeThemeData with Diagnosticable {
95101
largeSize: lerpDouble(a?.largeSize, b?.largeSize, t),
96102
textStyle: TextStyle.lerp(a?.textStyle, b?.textStyle, t),
97103
padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t),
98-
alignment: AlignmentDirectional.lerp(a?.alignment, b?.alignment, t),
104+
alignment: AlignmentGeometry.lerp(a?.alignment, b?.alignment, t),
105+
offset: Offset.lerp(a?.offset, b?.offset, t),
99106
);
100107
}
101108

@@ -108,6 +115,7 @@ class BadgeThemeData with Diagnosticable {
108115
textStyle,
109116
padding,
110117
alignment,
118+
offset,
111119
);
112120

113121
@override
@@ -125,7 +133,8 @@ class BadgeThemeData with Diagnosticable {
125133
&& other.largeSize == largeSize
126134
&& other.textStyle == textStyle
127135
&& other.padding == padding
128-
&& other.alignment == alignment;
136+
&& other.alignment == alignment
137+
&& other.offset == offset;
129138
}
130139

131140
@override
@@ -137,7 +146,8 @@ class BadgeThemeData with Diagnosticable {
137146
properties.add(DoubleProperty('largeSize', largeSize, defaultValue: null));
138147
properties.add(DiagnosticsProperty<TextStyle>('textStyle', textStyle, defaultValue: null));
139148
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
140-
properties.add(DiagnosticsProperty<AlignmentDirectional>('alignment', alignment, defaultValue: null));
149+
properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null));
150+
properties.add(DiagnosticsProperty<Offset>('offset', offset, defaultValue: null));
141151
}
142152
}
143153

0 commit comments

Comments
 (0)