Skip to content

Instantly share code, notes, and snippets.

@supernovel
Created October 2, 2019 14:59
Show Gist options
  • Save supernovel/ac822f2ff6015cbec07fd5d96d57d712 to your computer and use it in GitHub Desktop.
Save supernovel/ac822f2ff6015cbec07fd5d96d57d712 to your computer and use it in GitHub Desktop.
flutter outline label box
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;
}
@supernovel
Copy link
Author

CustomPaint(
  foregroundPainter: OutlineLabelBox(
    labelText: "Label",
    labelStyle: TextStyle(
      color: Colors.white, 
      fontSize: 12, 
      fontWeight: FontWeight.bold,
    ),
    strokeColor: Color(0xff707070)),
  child: Container(
    width: 200,
    alignment: Alignment.center,
    padding: EdgeInsets.all(20),
    child: Text('Child'),
  )
)

outlineLabel

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment