Last active
May 26, 2022 10:48
-
-
Save pskink/314911dc93fffc39e5855a90b1ca27cb to your computer and use it in GitHub Desktop.
This file contains hidden or 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 'dart:ui'; | |
| import 'dart:math'; | |
| import 'package:flutter/material.dart'; | |
| /* | |
| class CornerDecorationTest extends StatefulWidget { | |
| @override | |
| _CornerDecorationTestState createState() => _CornerDecorationTestState(); | |
| } | |
| class _CornerDecorationTestState extends State<CornerDecorationTest> { | |
| bool checked = false; | |
| @override | |
| Widget build(BuildContext context) { | |
| return AnimatedContainer( | |
| duration: const Duration(milliseconds: 350), | |
| decoration: CornerDecoration( | |
| strokeWidth: checked? 4 : 1, | |
| strokeColor: checked? Colors.orange : Colors.black, | |
| insets: checked? const EdgeInsets.all(16) : const EdgeInsets.symmetric(vertical: 48 * 2.0, horizontal: 48), | |
| cornerSide: checked? CornerSide.all(16, 48, Radius.circular(8)) : CornerSide.vertical(top: const Size.square(16)), | |
| fillTop: checked? 0.0 : 1.0, | |
| fillBottom: checked? 0.0 : 1.0, | |
| ), | |
| child: Container( | |
| constraints: const BoxConstraints.expand(), | |
| decoration: const FlutterLogoDecoration(style: FlutterLogoStyle.stacked), | |
| child: OutlinedButton( | |
| onPressed: () => setState(() => checked = !checked), | |
| child: const Align(alignment: Alignment.centerLeft, child: Text('press me')), | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| */ | |
| // ============================================================================ | |
| // ============================================================================ | |
| // ============================================================================ | |
| class CornerDecoration extends Decoration { | |
| final double fillLeft; | |
| final double fillTop; | |
| final double fillRight; | |
| final double fillBottom; | |
| final double strokeWidth; | |
| final Color strokeColor; | |
| final EdgeInsets insets; | |
| final double position; | |
| final CornerSide cornerSide; | |
| CornerDecoration({ | |
| this.fillLeft = 0.0, | |
| this.fillTop = 0.0, | |
| this.fillRight = 0.0, | |
| this.fillBottom = 0.0, | |
| this.strokeWidth = 3, | |
| required this.strokeColor, | |
| EdgeInsets? insets, | |
| this.position = 0.5, | |
| this.cornerSide = const CornerSide._all(Size.square(16), Radius.zero), | |
| }) : insets = insets ?? EdgeInsets.all(strokeWidth), | |
| assert(0 <= fillLeft && fillLeft <= 1), | |
| assert(0 <= fillTop && fillTop <= 1), | |
| assert(0 <= fillRight && fillRight <= 1), | |
| assert(0 <= fillBottom && fillBottom <= 1); | |
| @override | |
| BoxPainter createBoxPainter([VoidCallback? onChanged]) => | |
| _CornerBoxPainter(fillLeft, fillTop, fillRight, fillBottom, strokeWidth, strokeColor, insets, position, cornerSide); | |
| @override | |
| EdgeInsetsGeometry get padding => insets; | |
| @override | |
| Decoration? lerpFrom(Decoration? a, double t) { | |
| if (a is CornerDecoration) { | |
| return CornerDecoration( | |
| fillLeft: lerpDouble(a.fillLeft, fillLeft, t)!, | |
| fillTop: lerpDouble(a.fillTop, fillTop, t)!, | |
| fillRight: lerpDouble(a.fillRight, fillRight, t)!, | |
| fillBottom: lerpDouble(a.fillBottom, fillBottom, t)!, | |
| strokeWidth: lerpDouble(a.strokeWidth, strokeWidth, t)!, | |
| strokeColor: Color.lerp(a.strokeColor, strokeColor, t)!, | |
| insets: EdgeInsets.lerp(a.insets, insets, t), | |
| position: lerpDouble(a.position, position, t)!, | |
| cornerSide: CornerSide.lerp(a.cornerSide, cornerSide, t), | |
| ); | |
| } | |
| return super.lerpFrom(a, t) as BoxDecoration?; | |
| } | |
| @override | |
| bool operator ==(Object other) { | |
| if (identical(this, other)) | |
| return true; | |
| if (other.runtimeType != runtimeType) | |
| return false; | |
| return other is CornerDecoration | |
| && other.fillLeft == fillLeft | |
| && other.fillTop == fillTop | |
| && other.fillRight == fillRight | |
| && other.fillBottom == fillBottom | |
| && other.strokeWidth == strokeWidth | |
| && other.strokeColor == strokeColor | |
| && other.insets == insets | |
| && other.position == position | |
| && other.cornerSide == cornerSide; | |
| } | |
| @override | |
| int get hashCode { | |
| return hashValues( | |
| fillLeft, | |
| fillTop, | |
| fillRight, | |
| fillBottom, | |
| strokeWidth, | |
| strokeColor, | |
| insets, | |
| position, | |
| cornerSide, | |
| ); | |
| } | |
| } | |
| class CornerSide { | |
| CornerSide.all(double x, [double? y, Radius radius = Radius.zero]) : this._all(Size(x, y ?? x), radius); | |
| CornerSide.vertical({ | |
| Size top = Size.zero, | |
| Size bottom = Size.zero, | |
| Radius topRadius = Radius.zero, | |
| Radius bottomRadius = Radius.zero, | |
| }) : this.only( | |
| topLeft: top, | |
| topRight: top, | |
| bottomLeft: bottom, | |
| bottomRight: bottom, | |
| topLeftRadius: topRadius, | |
| topRightRadius: topRadius, | |
| bottomLeftRadius: bottomRadius, | |
| bottomRightRadius: bottomRadius, | |
| ); | |
| CornerSide.horizontal({ | |
| Size left = Size.zero, | |
| Size right = Size.zero, | |
| Radius leftRadius = Radius.zero, | |
| Radius rightRadius = Radius.zero, | |
| }) : this.only( | |
| topLeft: left, | |
| topRight: right, | |
| bottomLeft: left, | |
| bottomRight: right, | |
| topLeftRadius: leftRadius, | |
| topRightRadius: rightRadius, | |
| bottomLeftRadius: leftRadius, | |
| bottomRightRadius: rightRadius, | |
| ); | |
| const CornerSide.only({ | |
| this.topLeft = Size.zero, | |
| this.topRight = Size.zero, | |
| this.bottomLeft = Size.zero, | |
| this.bottomRight = Size.zero, | |
| this.topLeftRadius = Radius.zero, | |
| this.topRightRadius = Radius.zero, | |
| this.bottomLeftRadius = Radius.zero, | |
| this.bottomRightRadius = Radius.zero, | |
| }); | |
| const CornerSide._all(Size size, Radius radius) : this.only( | |
| topLeft: size, | |
| topRight: size, | |
| bottomLeft: size, | |
| bottomRight: size, | |
| topLeftRadius: radius, | |
| topRightRadius: radius, | |
| bottomLeftRadius: radius, | |
| bottomRightRadius: radius, | |
| ); | |
| final Size topLeft; | |
| final Size topRight; | |
| final Size bottomLeft; | |
| final Size bottomRight; | |
| final Radius topLeftRadius; | |
| final Radius topRightRadius; | |
| final Radius bottomLeftRadius; | |
| final Radius bottomRightRadius; | |
| static CornerSide lerp(CornerSide a, CornerSide b, double t) { | |
| return CornerSide.only( | |
| topLeft: Size.lerp(a.topLeft, b.topLeft, t)!, | |
| topRight: Size.lerp(a.topRight, b.topRight, t)!, | |
| bottomRight: Size.lerp(a.bottomRight, b.bottomRight, t)!, | |
| bottomLeft: Size.lerp(a.bottomLeft, b.bottomLeft, t)!, | |
| topLeftRadius: Radius.lerp(a.topLeftRadius, b.topLeftRadius, t)!, | |
| topRightRadius: Radius.lerp(a.topRightRadius, b.topRightRadius, t)!, | |
| bottomRightRadius: Radius.lerp(a.bottomRightRadius, b.bottomRightRadius, t)!, | |
| bottomLeftRadius: Radius.lerp(a.bottomLeftRadius, b.bottomLeftRadius, t)!, | |
| ); | |
| } | |
| @override | |
| bool operator ==(Object other) { | |
| if (identical(this, other)) | |
| return true; | |
| if (other.runtimeType != runtimeType) | |
| return false; | |
| return other is CornerSide | |
| && other.topLeft == topLeft | |
| && other.topRight == topRight | |
| && other.bottomLeft == bottomLeft | |
| && other.bottomRight == bottomRight | |
| && other.topLeftRadius == topLeftRadius | |
| && other.topRightRadius == topRightRadius | |
| && other.bottomLeftRadius == bottomLeftRadius | |
| && other.bottomRightRadius == bottomRightRadius; | |
| } | |
| @override | |
| int get hashCode { | |
| return hashValues( | |
| topLeft, | |
| topRight, | |
| bottomLeft, | |
| bottomRight, | |
| topLeftRadius, | |
| topRightRadius, | |
| bottomLeftRadius, | |
| bottomRightRadius, | |
| ); | |
| } | |
| } | |
| class _CornerBoxPainter extends BoxPainter { | |
| final double fillLeft; | |
| final double fillTop; | |
| final double fillRight; | |
| final double fillBottom; | |
| final double strokeWidth; | |
| final EdgeInsets insets; | |
| final double position; | |
| final CornerSide cornerSide; | |
| late Paint _p; | |
| _CornerBoxPainter(this.fillLeft, this.fillTop, this.fillRight, this.fillBottom, this.strokeWidth, strokeColor, this.insets, this.position, this.cornerSide) { | |
| _p = Paint() | |
| ..color = strokeColor | |
| ..style = PaintingStyle.stroke | |
| // ..strokeCap = StrokeCap.square | |
| ..strokeWidth = strokeWidth; | |
| } | |
| @override | |
| void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { | |
| final _rect = offset & configuration.size!; | |
| final a = _rect.deflate(strokeWidth / 2); | |
| final b = insets.deflateRect(_rect).inflate(strokeWidth / 2); | |
| final rect = Rect.lerp(a, b, position)!; | |
| Path _path = Path(); | |
| final topLeftCornerRect = Alignment.topLeft.inscribe(cornerSide.topLeft, rect); | |
| final topRightCornerRect = Alignment.topRight.inscribe(cornerSide.topRight, rect); | |
| final bottomRightCornerRect = Alignment.bottomRight.inscribe(cornerSide.bottomRight, rect); | |
| final bottomLeftCornerRect = Alignment.bottomLeft.inscribe(cornerSide.bottomLeft, rect); | |
| final i = <dynamic>[ | |
| Offset.lerp(topLeftCornerRect.bottomLeft, rect.centerLeft, fillLeft)!, | |
| _midPoint(rect, cornerSide.topLeftRadius, Alignment.topLeft, -pi), | |
| Offset.lerp(topLeftCornerRect.topRight, rect.topCenter, fillTop)!, | |
| Offset.lerp(topRightCornerRect.topLeft, rect.topCenter, fillTop)!, | |
| _midPoint(rect, cornerSide.topRightRadius, Alignment.topRight, -pi / 2), | |
| Offset.lerp(topRightCornerRect.bottomRight, rect.centerRight, fillRight)!, | |
| Offset.lerp(bottomRightCornerRect.topRight, rect.centerRight, fillRight)!, | |
| _midPoint(rect, cornerSide.bottomRightRadius, Alignment.bottomRight, 0), | |
| Offset.lerp(bottomRightCornerRect.bottomLeft, rect.bottomCenter, fillBottom)!, | |
| Offset.lerp(bottomLeftCornerRect.bottomRight, rect.bottomCenter, fillBottom)!, | |
| _midPoint(rect, cornerSide.bottomLeftRadius, Alignment.bottomLeft, pi / 2), | |
| Offset.lerp(bottomLeftCornerRect.topLeft, rect.centerLeft, fillLeft)!, | |
| ].iterator; | |
| while (i.moveNext()) { | |
| final point0 = i.current as Offset; | |
| _path.moveTo(point0.dx, point0.dy); i.moveNext(); | |
| final arcEntry = i.current as _ArcEntry; | |
| arcEntry.addToPath(_path); i.moveNext(); | |
| final point1 = i.current as Offset; | |
| _path.lineTo(point1.dx, point1.dy); | |
| } | |
| canvas.drawPath(_path, _p); | |
| } | |
| _ArcEntry _midPoint(Rect rect, Radius radius, Alignment alignmnet, double startAngle) { | |
| return radius != Radius.zero? | |
| _ArcEntry.arc(alignmnet.inscribe(Size(radius.x * 2, radius.y * 2), rect), startAngle) : | |
| _ArcEntry.point(alignmnet.withinRect(rect)); | |
| } | |
| } | |
| class _ArcEntry { | |
| final bool sharp; | |
| final Offset point; | |
| final Rect rect; | |
| final double startAngle; | |
| _ArcEntry.point(this.point) : sharp = true, rect = Rect.zero, startAngle = 0; | |
| _ArcEntry.arc(this.rect, this.startAngle) : sharp = false, point = Offset.zero; | |
| void addToPath(Path path) { | |
| if (sharp) { | |
| path.lineTo(point.dx, point.dy); | |
| } else { | |
| path.arcTo(rect, startAngle, pi / 2, false); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment