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' ;
56import 'package:flutter/widgets.dart' ;
67
78import '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;
0 commit comments