@@ -212,7 +212,9 @@ abstract class BoxBorder extends ShapeBorder {
212212 ///
213213 /// See also:
214214 ///
215- /// * [paintBorder] , which is used if the border is not uniform.
215+ /// * [paintBorder] , which is used if the border has non-uniform colors or styles and no borderRadius.
216+ /// * [Border.paint] , similar to this method, includes additional comments
217+ /// and provides more details on each parameter than described here.
216218 @override
217219 void paint (
218220 Canvas canvas,
@@ -240,6 +242,62 @@ abstract class BoxBorder extends ShapeBorder {
240242 }
241243 }
242244
245+ static void _paintNonUniformBorder (
246+ Canvas canvas,
247+ Rect rect, {
248+ required BorderRadius ? borderRadius,
249+ required BoxShape shape,
250+ required TextDirection ? textDirection,
251+ required BorderSide left,
252+ required BorderSide top,
253+ required BorderSide right,
254+ required BorderSide bottom,
255+ }) {
256+ final RRect borderRect;
257+ switch (shape) {
258+ case BoxShape .rectangle:
259+ borderRect = (borderRadius ?? BorderRadius .zero)
260+ .resolve (textDirection)
261+ .toRRect (rect);
262+ case BoxShape .circle:
263+ assert (borderRadius == null , 'A borderRadius cannot be given when shape is a BoxShape.circle.' );
264+ borderRect = RRect .fromRectAndRadius (
265+ Rect .fromCircle (center: rect.center, radius: rect.shortestSide / 2.0 ),
266+ Radius .circular (rect.width),
267+ );
268+ }
269+ final Paint paint = Paint ()..color = top.color;
270+ final RRect inner = _deflateRRect (borderRect, EdgeInsets .fromLTRB (left.strokeInset, top.strokeInset, right.strokeInset, bottom.strokeInset));
271+ final RRect outer = _inflateRRect (borderRect, EdgeInsets .fromLTRB (left.strokeOutset, top.strokeOutset, right.strokeOutset, bottom.strokeOutset));
272+ canvas.drawDRRect (outer, inner, paint);
273+ }
274+
275+ static RRect _inflateRRect (RRect rect, EdgeInsets insets) {
276+ return RRect .fromLTRBAndCorners (
277+ rect.left - insets.left,
278+ rect.top - insets.top,
279+ rect.right + insets.right,
280+ rect.bottom + insets.bottom,
281+ topLeft: (rect.tlRadius + Radius .elliptical (insets.left, insets.top)).clamp (minimum: Radius .zero), // ignore_clamp_double_lint
282+ topRight: (rect.trRadius + Radius .elliptical (insets.right, insets.top)).clamp (minimum: Radius .zero), // ignore_clamp_double_lint
283+ bottomRight: (rect.brRadius + Radius .elliptical (insets.right, insets.bottom)).clamp (minimum: Radius .zero), // ignore_clamp_double_lint
284+ bottomLeft: (rect.blRadius + Radius .elliptical (insets.left, insets.bottom)).clamp (minimum: Radius .zero), // ignore_clamp_double_lint
285+ );
286+ }
287+
288+ static RRect _deflateRRect (RRect rect, EdgeInsets insets) {
289+ return RRect .fromLTRBAndCorners (
290+ rect.left + insets.left,
291+ rect.top + insets.top,
292+ rect.right - insets.right,
293+ rect.bottom - insets.bottom,
294+ topLeft: (rect.tlRadius - Radius .elliptical (insets.left, insets.top)).clamp (minimum: Radius .zero), // ignore_clamp_double_lint
295+ topRight: (rect.trRadius - Radius .elliptical (insets.right, insets.top)).clamp (minimum: Radius .zero), // ignore_clamp_double_lint
296+ bottomRight: (rect.brRadius - Radius .elliptical (insets.right, insets.bottom)).clamp (minimum: Radius .zero), // ignore_clamp_double_lint
297+ bottomLeft: (rect.blRadius - Radius .elliptical (insets.left, insets.bottom)).clamp (minimum: Radius .zero), // ignore_clamp_double_lint
298+ );
299+ }
300+
243301 static void _paintUniformBorderWithCircle (Canvas canvas, Rect rect, BorderSide side) {
244302 assert (side.style != BorderStyle .none);
245303 final double radius = (rect.shortestSide + side.strokeOffset) / 2 ;
@@ -313,6 +371,10 @@ abstract class BoxBorder extends ShapeBorder {
313371/// * [BorderSide] , which is used to describe each side of the box.
314372/// * [Theme] , from the material layer, which can be queried to obtain appropriate colors
315373/// to use for borders in a [MaterialApp], as shown in the "divider" sample above.
374+ /// * [paint] , which explains the behavior of [BoxDecoration] parameters.
375+ /// * <https://pub.dev/packages/non_uniform_border>, a package that implements
376+ /// a Non-Uniform Border on ShapeBorder, which is used by Material Design
377+ /// buttons and other widgets, under the "shape" field.
316378class Border extends BoxBorder {
317379 /// Creates a border.
318380 ///
@@ -407,24 +469,24 @@ class Border extends BoxBorder {
407469
408470 bool get _colorIsUniform {
409471 final Color topColor = top.color;
410- return right .color == topColor && bottom.color == topColor && left .color == topColor;
472+ return left .color == topColor && bottom.color == topColor && right .color == topColor;
411473 }
412474
413475 bool get _widthIsUniform {
414476 final double topWidth = top.width;
415- return right .width == topWidth && bottom.width == topWidth && left .width == topWidth;
477+ return left .width == topWidth && bottom.width == topWidth && right .width == topWidth;
416478 }
417479
418480 bool get _styleIsUniform {
419481 final BorderStyle topStyle = top.style;
420- return right .style == topStyle && bottom.style == topStyle && left .style == topStyle;
482+ return left .style == topStyle && bottom.style == topStyle && right .style == topStyle;
421483 }
422484
423485 bool get _strokeAlignIsUniform {
424486 final double topStrokeAlign = top.strokeAlign;
425- return right .strokeAlign == topStrokeAlign
487+ return left .strokeAlign == topStrokeAlign
426488 && bottom.strokeAlign == topStrokeAlign
427- && left .strokeAlign == topStrokeAlign;
489+ && right .strokeAlign == topStrokeAlign;
428490 }
429491
430492 @override
@@ -491,14 +553,16 @@ class Border extends BoxBorder {
491553
492554 /// Paints the border within the given [Rect] on the given [Canvas] .
493555 ///
494- /// Uniform borders are more efficient to paint than more complex borders.
556+ /// Uniform borders and non-uniform borders with similar colors and styles
557+ /// are more efficient to paint than more complex borders.
495558 ///
496559 /// You can provide a [BoxShape] to draw the border on. If the `shape` in
497- /// [BoxShape.circle] , there is the requirement that the border [isUniform] .
560+ /// [BoxShape.circle] , there is the requirement that the border has uniform
561+ /// color and style.
498562 ///
499563 /// If you specify a rectangular box shape ([BoxShape.rectangle] ), then you
500564 /// may specify a [BorderRadius] . If a `borderRadius` is specified, there is
501- /// the requirement that the border [isUniform] .
565+ /// the requirement that the border has uniform color and style .
502566 ///
503567 /// The [getInnerPath] and [getOuterPath] methods do not know about the
504568 /// `shape` and `borderRadius` arguments.
@@ -507,7 +571,10 @@ class Border extends BoxBorder {
507571 ///
508572 /// See also:
509573 ///
510- /// * [paintBorder] , which is used if the border is not uniform.
574+ /// * [paintBorder] , which is used if the border has non-uniform colors or styles and no borderRadius.
575+ /// * <https://pub.dev/packages/non_uniform_border>, a package that implements
576+ /// a Non-Uniform Border on ShapeBorder, which is used by Material Design
577+ /// buttons and other widgets, under the "shape" field.
511578 @override
512579 void paint (
513580 Canvas canvas,
@@ -523,7 +590,7 @@ class Border extends BoxBorder {
523590 case BorderStyle .solid:
524591 switch (shape) {
525592 case BoxShape .circle:
526- assert (borderRadius == null , 'A borderRadius can only be given for rectangular boxes .' );
593+ assert (borderRadius == null , 'A borderRadius cannot be given when shape is a BoxShape.circle .' );
527594 BoxBorder ._paintUniformBorderWithCircle (canvas, rect, top);
528595 case BoxShape .rectangle:
529596 if (borderRadius != null && borderRadius != BorderRadius .zero) {
@@ -536,36 +603,50 @@ class Border extends BoxBorder {
536603 }
537604 }
538605
606+ // Allow painting non-uniform borders if the color and style are uniform.
607+ if (_colorIsUniform && _styleIsUniform) {
608+ switch (top.style) {
609+ case BorderStyle .none:
610+ return ;
611+ case BorderStyle .solid:
612+ BoxBorder ._paintNonUniformBorder (canvas, rect,
613+ shape: shape,
614+ borderRadius: borderRadius,
615+ textDirection: textDirection,
616+ left: left,
617+ top: top,
618+ right: right,
619+ bottom: bottom);
620+ return ;
621+ }
622+ }
623+
539624 assert (() {
540625 if (borderRadius != null ) {
541626 throw FlutterError .fromParts (< DiagnosticsNode > [
542- ErrorSummary ('A borderRadius can only be given for a uniform Border .' ),
627+ ErrorSummary ('A borderRadius can only be given on borders with uniform colors and styles .' ),
543628 ErrorDescription ('The following is not uniform:' ),
544629 if (! _colorIsUniform) ErrorDescription ('BorderSide.color' ),
545- if (! _widthIsUniform) ErrorDescription ('BorderSide.width' ),
546630 if (! _styleIsUniform) ErrorDescription ('BorderSide.style' ),
547- if (! _strokeAlignIsUniform) ErrorDescription ('BorderSide.strokeAlign' ),
548631 ]);
549632 }
550633 return true ;
551634 }());
552635 assert (() {
553636 if (shape != BoxShape .rectangle) {
554637 throw FlutterError .fromParts (< DiagnosticsNode > [
555- ErrorSummary ('A Border can only be drawn as a circle if it is uniform.' ),
638+ ErrorSummary ('A Border can only be drawn as a circle on borders with uniform colors and styles .' ),
556639 ErrorDescription ('The following is not uniform:' ),
557640 if (! _colorIsUniform) ErrorDescription ('BorderSide.color' ),
558- if (! _widthIsUniform) ErrorDescription ('BorderSide.width' ),
559641 if (! _styleIsUniform) ErrorDescription ('BorderSide.style' ),
560- if (! _strokeAlignIsUniform) ErrorDescription ('BorderSide.strokeAlign' ),
561642 ]);
562643 }
563644 return true ;
564645 }());
565646 assert (() {
566647 if (! _strokeAlignIsUniform || top.strokeAlign != BorderSide .strokeAlignInside) {
567648 throw FlutterError .fromParts (< DiagnosticsNode > [
568- ErrorSummary ('A Border can only draw strokeAlign different than BorderSide.strokeAlignInside on uniform borders .' ),
649+ ErrorSummary ('A Border can only draw strokeAlign different than BorderSide.strokeAlignInside on borders with uniform colors and styles .' ),
569650 ]);
570651 }
571652 return true ;
@@ -626,6 +707,9 @@ class Border extends BoxBorder {
626707/// * [BorderSide] , which is used to describe each side of the box.
627708/// * [Theme] , from the material layer, which can be queried to obtain appropriate colors
628709/// to use for borders in a [MaterialApp], as shown in the "divider" sample above.
710+ /// * <https://pub.dev/packages/non_uniform_border>, a package that implements
711+ /// a Non-Uniform Border on ShapeBorder, which is used by Material Design
712+ /// buttons and other widgets, under the "shape" field.
629713class BorderDirectional extends BoxBorder {
630714 /// Creates a border.
631715 ///
@@ -698,33 +782,21 @@ class BorderDirectional extends BoxBorder {
698782 }
699783
700784 @override
701- bool get isUniform {
785+ bool get isUniform => _colorIsUniform && _widthIsUniform && _styleIsUniform && _strokeAlignIsUniform;
786+
787+ bool get _colorIsUniform {
702788 final Color topColor = top.color;
703- if (start.color != topColor ||
704- end.color != topColor ||
705- bottom.color != topColor) {
706- return false ;
707- }
789+ return start.color == topColor && bottom.color == topColor && end.color == topColor;
790+ }
708791
792+ bool get _widthIsUniform {
709793 final double topWidth = top.width;
710- if (start.width != topWidth ||
711- end.width != topWidth ||
712- bottom.width != topWidth) {
713- return false ;
714- }
794+ return start.width == topWidth && bottom.width == topWidth && end.width == topWidth;
795+ }
715796
797+ bool get _styleIsUniform {
716798 final BorderStyle topStyle = top.style;
717- if (start.style != topStyle ||
718- end.style != topStyle ||
719- bottom.style != topStyle) {
720- return false ;
721- }
722-
723- if (_strokeAlignIsUniform == false ) {
724- return false ;
725- }
726-
727- return true ;
799+ return start.style == topStyle && bottom.style == topStyle && end.style == topStyle;
728800 }
729801
730802 bool get _strokeAlignIsUniform {
@@ -850,7 +922,7 @@ class BorderDirectional extends BoxBorder {
850922 ///
851923 /// See also:
852924 ///
853- /// * [paintBorder] , which is used if the border is not uniform.
925+ /// * [paintBorder] , which is used if the border has non- uniform colors or styles and no borderRadius .
854926 @override
855927 void paint (
856928 Canvas canvas,
@@ -866,7 +938,7 @@ class BorderDirectional extends BoxBorder {
866938 case BorderStyle .solid:
867939 switch (shape) {
868940 case BoxShape .circle:
869- assert (borderRadius == null , 'A borderRadius can only be given for rectangular boxes .' );
941+ assert (borderRadius == null , 'A borderRadius cannot be given when shape is a BoxShape.circle .' );
870942 BoxBorder ._paintUniformBorderWithCircle (canvas, rect, top);
871943 case BoxShape .rectangle:
872944 if (borderRadius != null && borderRadius != BorderRadius .zero) {
@@ -879,10 +951,6 @@ class BorderDirectional extends BoxBorder {
879951 }
880952 }
881953
882- assert (borderRadius == null , 'A borderRadius can only be given for uniform borders.' );
883- assert (shape == BoxShape .rectangle, 'A border can only be drawn as a circle if it is uniform.' );
884- assert (_strokeAlignIsUniform && top.strokeAlign == BorderSide .strokeAlignInside, 'A Border can only draw strokeAlign different than strokeAlignInside on uniform borders.' );
885-
886954 final BorderSide left, right;
887955 assert (textDirection != null , 'Non-uniform BorderDirectional objects require a TextDirection when painting.' );
888956 switch (textDirection! ) {
@@ -893,6 +961,29 @@ class BorderDirectional extends BoxBorder {
893961 left = start;
894962 right = end;
895963 }
964+
965+ // Allow painting non-uniform borders if the color and style are uniform.
966+ if (_colorIsUniform && _styleIsUniform) {
967+ switch (top.style) {
968+ case BorderStyle .none:
969+ return ;
970+ case BorderStyle .solid:
971+ BoxBorder ._paintNonUniformBorder (canvas, rect,
972+ shape: shape,
973+ borderRadius: borderRadius,
974+ textDirection: textDirection,
975+ left: left,
976+ top: top,
977+ right: right,
978+ bottom: bottom);
979+ return ;
980+ }
981+ }
982+
983+ assert (borderRadius == null , 'A borderRadius can only be given for borders with uniform colors and styles.' );
984+ assert (shape == BoxShape .rectangle, 'A Border can only be drawn as a circle on borders with uniform colors and styles.' );
985+ assert (_strokeAlignIsUniform && top.strokeAlign == BorderSide .strokeAlignInside, 'A Border can only draw strokeAlign different than strokeAlignInside on borders with uniform colors and styles.' );
986+
896987 paintBorder (canvas, rect, top: top, left: left, bottom: bottom, right: right);
897988 }
898989
0 commit comments