Created
October 2, 2019 14:59
-
-
Save supernovel/ac822f2ff6015cbec07fd5d96d57d712 to your computer and use it in GitHub Desktop.
flutter outline label box
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
import 'dart:math' as math; | |
import 'package:flutter/semantics.dart'; | |
class OutlineLabelBox extends CustomPainter { | |
final String labelText; | |
final TextStyle labelStyle; | |
final double labelPadding; | |
final Color strokeColor; | |
final double strokeWidth; | |
OutlineLabelBox( | |
{this.labelText, | |
this.labelStyle, | |
this.labelPadding = 12.0, | |
this.strokeColor = Colors.black, | |
this.strokeWidth = 1.0}); | |
@override | |
void paint(Canvas canvas, Size size) { | |
final TextPainter tp = TextPainter( | |
text: TextSpan( | |
text: this.labelText, | |
style: this | |
.labelStyle | |
.merge(TextStyle(textBaseline: TextBaseline.ideographic))), | |
textDirection: TextDirection.ltr, | |
textAlign: TextAlign.start) | |
..layout(); | |
final RRect outer = RRect.fromRectAndCorners( | |
Offset.zero & size, | |
); | |
final RRect center = outer.deflate(1 / 2.0); | |
final Path path = _gapBorderPath( | |
canvas, | |
center, | |
(size.width - tp.width) / 2.0 - labelPadding, | |
tp.width + (labelPadding * 2)); | |
canvas.drawPath( | |
path, | |
Paint() | |
..color = this.strokeColor | |
..strokeWidth = this.strokeWidth | |
..style = PaintingStyle.stroke); | |
tp.paint(canvas, Offset((size.width - tp.width) / 2.0, -(tp.height) / 2)); | |
} | |
Path _gapBorderPath( | |
Canvas canvas, RRect center, double start, double extent) { | |
final Rect tlCorner = Rect.fromLTWH( | |
center.left, | |
center.top, | |
center.tlRadiusX * 2.0, | |
center.tlRadiusY * 2.0, | |
); | |
final Rect trCorner = Rect.fromLTWH( | |
center.right - center.trRadiusX * 2.0, | |
center.top, | |
center.trRadiusX * 2.0, | |
center.trRadiusY * 2.0, | |
); | |
final Rect brCorner = Rect.fromLTWH( | |
center.right - center.brRadiusX * 2.0, | |
center.bottom - center.brRadiusY * 2.0, | |
center.brRadiusX * 2.0, | |
center.brRadiusY * 2.0, | |
); | |
final Rect blCorner = Rect.fromLTWH( | |
center.left, | |
center.bottom - center.brRadiusY * 2.0, | |
center.blRadiusX * 2.0, | |
center.blRadiusY * 2.0, | |
); | |
const double cornerArcSweep = math.pi / 2.0; | |
final double tlCornerArcSweep = start < center.tlRadiusX | |
? math.asin((start / center.tlRadiusX).clamp(-1.0, 1.0)) | |
: math.pi / 2.0; | |
final Path path = Path() | |
..addArc(tlCorner, math.pi, tlCornerArcSweep) | |
..moveTo(center.left + center.tlRadiusX, center.top); | |
if (start > center.tlRadiusX) path.lineTo(center.left + start, center.top); | |
const double trCornerArcStart = (3 * math.pi) / 2.0; | |
const double trCornerArcSweep = cornerArcSweep; | |
if (start + extent < center.width - center.trRadiusX) { | |
path | |
..relativeMoveTo(extent, 0.0) | |
..lineTo(center.right - center.trRadiusX, center.top) | |
..addArc(trCorner, trCornerArcStart, trCornerArcSweep); | |
} else if (start + extent < center.width) { | |
final double dx = center.width - (start + extent); | |
final double sweep = math.acos(dx / center.trRadiusX); | |
path.addArc(trCorner, trCornerArcStart + sweep, trCornerArcSweep - sweep); | |
} | |
return path | |
..moveTo(center.right, center.top + center.trRadiusY) | |
..lineTo(center.right, center.bottom - center.brRadiusY) | |
..addArc(brCorner, 0.0, cornerArcSweep) | |
..lineTo(center.left + center.blRadiusX, center.bottom) | |
..addArc(blCorner, math.pi / 2.0, cornerArcSweep) | |
..lineTo(center.left, center.top + center.trRadiusY); | |
} | |
double lerpDouble(num a, num b, double t) { | |
if (a == null && b == null) return null; | |
a ??= 0.0; | |
b ??= 0.0; | |
return a + (b - a) * t; | |
} | |
@override | |
SemanticsBuilderCallback get semanticsBuilder { | |
return (Size size) { | |
var rect = Offset.zero & size; | |
var width = size.shortestSide * 0.4; | |
rect = const Alignment(0.8, -0.9).inscribe(Size(width, width), rect); | |
return [ | |
CustomPainterSemantics( | |
rect: rect, | |
properties: SemanticsProperties( | |
label: this.labelText, | |
textDirection: TextDirection.ltr, | |
), | |
), | |
]; | |
}; | |
} | |
// Since this Sky painter has no fields, it always paints | |
// the same thing and semantics information is the same. | |
// Therefore we return false here. If we had fields (set | |
// from the constructor) then we would return true if any | |
// of them differed from the same fields on the oldDelegate. | |
@override | |
bool shouldRepaint(OutlineLabelBox oldDelegate) => false; | |
@override | |
bool shouldRebuildSemantics(OutlineLabelBox oldDelegate) => false; | |
} |
Author
supernovel
commented
Oct 2, 2019
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment