|
|
@@ -0,0 +1,505 @@
|
|
|
+import 'dart:math';
|
|
|
+import 'package:flutter/material.dart';
|
|
|
+
|
|
|
+class _BubbleBorderArrowProperties {
|
|
|
+ /// 箭头宽度的一半
|
|
|
+ final double halfWidth;
|
|
|
+
|
|
|
+ /// 箭头斜边的长度
|
|
|
+ final double hypotenuse;
|
|
|
+
|
|
|
+ /// 该斜边在主轴上的投影(水平时为X轴)
|
|
|
+ final double projectionOnMain;
|
|
|
+
|
|
|
+ /// 该斜边在纵轴上的投影(水平时为Y轴)
|
|
|
+ final double projectionOnCross;
|
|
|
+
|
|
|
+ /// 计算箭头半径在主轴上的投影(水平时为X轴)
|
|
|
+ final double arrowProjectionOnMain;
|
|
|
+
|
|
|
+ /// 计算箭头半径尖尖的长度
|
|
|
+ final double topLen;
|
|
|
+
|
|
|
+ _BubbleBorderArrowProperties({
|
|
|
+ required this.halfWidth,
|
|
|
+ required this.hypotenuse,
|
|
|
+ required this.projectionOnMain,
|
|
|
+ required this.projectionOnCross,
|
|
|
+ required this.arrowProjectionOnMain,
|
|
|
+ required this.topLen,
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+class BubbleShapeBorder extends OutlinedBorder {
|
|
|
+ final BorderRadius borderRadius;
|
|
|
+ final AxisDirection arrowDirection;
|
|
|
+ final double arrowLength;
|
|
|
+ final double arrowWidth;
|
|
|
+ final double arrowRadius;
|
|
|
+ final double? arrowOffset;
|
|
|
+ final Color? fillColor;
|
|
|
+
|
|
|
+ const BubbleShapeBorder({
|
|
|
+ super.side,
|
|
|
+ required this.arrowDirection,
|
|
|
+ this.borderRadius = BorderRadius.zero,
|
|
|
+ this.arrowLength = 12,
|
|
|
+ this.arrowWidth = 18,
|
|
|
+ this.arrowRadius = 3,
|
|
|
+ this.arrowOffset,
|
|
|
+ this.fillColor,
|
|
|
+ });
|
|
|
+
|
|
|
+ @override
|
|
|
+ OutlinedBorder copyWith({
|
|
|
+ AxisDirection? arrowDirection,
|
|
|
+ BorderSide? side,
|
|
|
+ BorderRadius? borderRadius,
|
|
|
+ double? arrowLength,
|
|
|
+ double? arrowWidth,
|
|
|
+ double? arrowRadius,
|
|
|
+ double? arrowOffset,
|
|
|
+ Color? fillColor,
|
|
|
+ }) {
|
|
|
+ return BubbleShapeBorder(
|
|
|
+ arrowDirection: arrowDirection ?? this.arrowDirection,
|
|
|
+ side: side ?? this.side,
|
|
|
+ borderRadius: borderRadius ?? this.borderRadius,
|
|
|
+ arrowLength: arrowLength ?? this.arrowLength,
|
|
|
+ arrowWidth: arrowWidth ?? this.arrowWidth,
|
|
|
+ arrowRadius: arrowRadius ?? this.arrowRadius,
|
|
|
+ arrowOffset: arrowOffset ?? this.arrowOffset,
|
|
|
+ fillColor: fillColor ?? this.fillColor,
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ EdgeInsetsGeometry get dimensions => EdgeInsets.zero;
|
|
|
+
|
|
|
+ @override
|
|
|
+ Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
|
|
|
+ return _buildPath(rect);
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
|
|
|
+ return _buildPath(rect);
|
|
|
+ }
|
|
|
+
|
|
|
+ _BubbleBorderArrowProperties _calculateArrowProperties() {
|
|
|
+ final arrowHalfWidth = arrowWidth / 2;
|
|
|
+ final double hypotenuse = sqrt(
|
|
|
+ arrowLength * arrowLength + arrowHalfWidth * arrowHalfWidth,
|
|
|
+ );
|
|
|
+ final double projectionOnMain = arrowHalfWidth * arrowRadius / hypotenuse;
|
|
|
+ final double projectionOnCross =
|
|
|
+ projectionOnMain * arrowLength / arrowHalfWidth;
|
|
|
+ final double arrowProjectionOnMain = arrowLength * arrowRadius / hypotenuse;
|
|
|
+ final double pointArrowTopLen =
|
|
|
+ arrowProjectionOnMain * arrowLength / arrowHalfWidth;
|
|
|
+ return _BubbleBorderArrowProperties(
|
|
|
+ halfWidth: arrowHalfWidth,
|
|
|
+ hypotenuse: hypotenuse,
|
|
|
+ projectionOnMain: projectionOnMain,
|
|
|
+ projectionOnCross: projectionOnCross,
|
|
|
+ arrowProjectionOnMain: arrowProjectionOnMain,
|
|
|
+ topLen: pointArrowTopLen,
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 核心逻辑:构建路径
|
|
|
+ /// 计算方向为:上、右、下、左
|
|
|
+ ///
|
|
|
+ /// 爱今天灵眸(ijtkj.cn),一款可根据天气改变系统显示模式的软件,期待各位有钱的码友支持
|
|
|
+ Path _buildPath(Rect rect) {
|
|
|
+ final path = Path();
|
|
|
+ EdgeInsets padding = EdgeInsets.zero;
|
|
|
+ if (arrowDirection == AxisDirection.up) {
|
|
|
+ padding = EdgeInsets.only(top: arrowLength);
|
|
|
+ } else if (arrowDirection == AxisDirection.right) {
|
|
|
+ padding = EdgeInsets.only(right: arrowLength);
|
|
|
+ } else if (arrowDirection == AxisDirection.down) {
|
|
|
+ padding = EdgeInsets.only(bottom: arrowLength);
|
|
|
+ } else if (arrowDirection == AxisDirection.left) {
|
|
|
+ padding = EdgeInsets.only(left: arrowLength);
|
|
|
+ }
|
|
|
+ final nRect = Rect.fromLTRB(
|
|
|
+ rect.left + padding.left,
|
|
|
+ rect.top + padding.top,
|
|
|
+ rect.right - padding.right,
|
|
|
+ rect.bottom - padding.bottom,
|
|
|
+ );
|
|
|
+
|
|
|
+ final arrowProp = _calculateArrowProperties();
|
|
|
+
|
|
|
+ final startPoint = Offset(nRect.left + borderRadius.topLeft.x, nRect.top);
|
|
|
+
|
|
|
+ path.moveTo(startPoint.dx, startPoint.dy);
|
|
|
+ // 箭头在上边
|
|
|
+ if (arrowDirection == AxisDirection.up) {
|
|
|
+ Offset pointCenter = Offset(
|
|
|
+ nRect.left + (arrowOffset ?? nRect.width / 2),
|
|
|
+ nRect.top,
|
|
|
+ );
|
|
|
+ Offset pointStart = Offset(
|
|
|
+ pointCenter.dx - arrowProp.halfWidth,
|
|
|
+ nRect.top,
|
|
|
+ );
|
|
|
+ Offset pointArrow = Offset(pointCenter.dx, rect.top);
|
|
|
+ Offset pointEnd = Offset(pointCenter.dx + arrowProp.halfWidth, nRect.top);
|
|
|
+
|
|
|
+ // 下面计算开始的圆弧
|
|
|
+ {
|
|
|
+ Offset pointStartArcBegin = Offset(
|
|
|
+ pointStart.dx - arrowRadius,
|
|
|
+ pointStart.dy,
|
|
|
+ );
|
|
|
+ Offset pointStartArcEnd = Offset(
|
|
|
+ pointStart.dx + arrowProp.projectionOnMain,
|
|
|
+ pointStart.dy - arrowProp.projectionOnCross,
|
|
|
+ );
|
|
|
+ path.lineTo(pointStartArcBegin.dx, pointStartArcBegin.dy);
|
|
|
+ path.quadraticBezierTo(
|
|
|
+ pointStart.dx,
|
|
|
+ pointStart.dy,
|
|
|
+ pointStartArcEnd.dx,
|
|
|
+ pointStartArcEnd.dy,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ // 计算中间箭头的圆弧
|
|
|
+ {
|
|
|
+ Offset pointArrowArcBegin = Offset(
|
|
|
+ pointArrow.dx - arrowProp.arrowProjectionOnMain,
|
|
|
+ pointArrow.dy + arrowProp.topLen,
|
|
|
+ );
|
|
|
+ Offset pointArrowArcEnd = Offset(
|
|
|
+ pointArrow.dx + arrowProp.arrowProjectionOnMain,
|
|
|
+ pointArrow.dy + arrowProp.topLen,
|
|
|
+ );
|
|
|
+ path.lineTo(pointArrowArcBegin.dx, pointArrowArcBegin.dy);
|
|
|
+ path.quadraticBezierTo(
|
|
|
+ pointArrow.dx,
|
|
|
+ pointArrow.dy,
|
|
|
+ pointArrowArcEnd.dx,
|
|
|
+ pointArrowArcEnd.dy,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ // 下面计算结束的圆弧
|
|
|
+ {
|
|
|
+ Offset pointEndArcBegin = Offset(
|
|
|
+ pointEnd.dx - arrowProp.projectionOnMain,
|
|
|
+ pointEnd.dy - arrowProp.projectionOnCross,
|
|
|
+ );
|
|
|
+ Offset pointEndArcEnd = Offset(pointEnd.dx + arrowRadius, pointEnd.dy);
|
|
|
+ path.lineTo(pointEndArcBegin.dx, pointEndArcBegin.dy);
|
|
|
+ path.quadraticBezierTo(
|
|
|
+ pointEnd.dx,
|
|
|
+ pointEnd.dy,
|
|
|
+ pointEndArcEnd.dx,
|
|
|
+ pointEndArcEnd.dy,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ path.lineTo(nRect.right - borderRadius.topRight.x, nRect.top);
|
|
|
+ // topRight radius
|
|
|
+ path.arcToPoint(
|
|
|
+ Offset(nRect.right, nRect.top + borderRadius.topRight.y),
|
|
|
+ radius: borderRadius.topRight,
|
|
|
+ rotation: 90,
|
|
|
+ );
|
|
|
+
|
|
|
+ // 箭头在右边
|
|
|
+ if (arrowDirection == AxisDirection.right) {
|
|
|
+ Offset pointCenter = Offset(
|
|
|
+ nRect.right,
|
|
|
+ nRect.top + (arrowOffset ?? nRect.height / 2),
|
|
|
+ );
|
|
|
+ Offset pointStart = Offset(
|
|
|
+ nRect.right,
|
|
|
+ pointCenter.dy - arrowProp.halfWidth,
|
|
|
+ );
|
|
|
+ Offset pointArrow = Offset(rect.right, pointCenter.dy);
|
|
|
+ Offset pointEnd = Offset(
|
|
|
+ nRect.right,
|
|
|
+ pointCenter.dy + arrowProp.halfWidth,
|
|
|
+ );
|
|
|
+
|
|
|
+ // 下面计算开始的圆弧
|
|
|
+ {
|
|
|
+ Offset pointStartArcBegin = Offset(
|
|
|
+ pointStart.dx,
|
|
|
+ pointStart.dy - arrowRadius,
|
|
|
+ );
|
|
|
+ Offset pointStartArcEnd = Offset(
|
|
|
+ pointStart.dx + arrowProp.projectionOnCross,
|
|
|
+ pointStart.dy + arrowProp.projectionOnMain,
|
|
|
+ );
|
|
|
+ path.lineTo(pointStartArcBegin.dx, pointStartArcBegin.dy);
|
|
|
+ path.quadraticBezierTo(
|
|
|
+ pointStart.dx,
|
|
|
+ pointStart.dy,
|
|
|
+ pointStartArcEnd.dx,
|
|
|
+ pointStartArcEnd.dy,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ // 计算中间箭头的圆弧
|
|
|
+ {
|
|
|
+ Offset pointArrowArcBegin = Offset(
|
|
|
+ pointArrow.dx - arrowProp.topLen,
|
|
|
+ pointArrow.dy - arrowProp.arrowProjectionOnMain,
|
|
|
+ );
|
|
|
+ Offset pointArrowArcEnd = Offset(
|
|
|
+ pointArrow.dx - arrowProp.topLen,
|
|
|
+ pointArrow.dy + arrowProp.arrowProjectionOnMain,
|
|
|
+ );
|
|
|
+ path.lineTo(pointArrowArcBegin.dx, pointArrowArcBegin.dy);
|
|
|
+ path.quadraticBezierTo(
|
|
|
+ pointArrow.dx,
|
|
|
+ pointArrow.dy,
|
|
|
+ pointArrowArcEnd.dx,
|
|
|
+ pointArrowArcEnd.dy,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ // 下面计算结束的圆弧
|
|
|
+ {
|
|
|
+ Offset pointEndArcBegin = Offset(
|
|
|
+ pointEnd.dx + arrowProp.projectionOnCross,
|
|
|
+ pointEnd.dy - arrowProp.projectionOnMain,
|
|
|
+ );
|
|
|
+ Offset pointEndArcEnd = Offset(pointEnd.dx, pointEnd.dy + arrowRadius);
|
|
|
+ path.lineTo(pointEndArcBegin.dx, pointEndArcBegin.dy);
|
|
|
+ path.quadraticBezierTo(
|
|
|
+ pointEnd.dx,
|
|
|
+ pointEnd.dy,
|
|
|
+ pointEndArcEnd.dx,
|
|
|
+ pointEndArcEnd.dy,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ path.lineTo(nRect.right, nRect.bottom - borderRadius.bottomRight.y);
|
|
|
+ // bottomRight radius
|
|
|
+ path.arcToPoint(
|
|
|
+ Offset(nRect.right - borderRadius.bottomRight.x, nRect.bottom),
|
|
|
+ radius: borderRadius.bottomRight,
|
|
|
+ rotation: 90,
|
|
|
+ );
|
|
|
+
|
|
|
+ // 箭头在下边
|
|
|
+ if (arrowDirection == AxisDirection.down) {
|
|
|
+ Offset pointCenter = Offset(
|
|
|
+ nRect.left + (arrowOffset ?? nRect.width / 2),
|
|
|
+ nRect.bottom,
|
|
|
+ );
|
|
|
+ Offset pointStart = Offset(
|
|
|
+ pointCenter.dx + arrowProp.halfWidth,
|
|
|
+ nRect.bottom,
|
|
|
+ );
|
|
|
+ Offset pointArrow = Offset(pointCenter.dx, rect.bottom);
|
|
|
+ Offset pointEnd = Offset(
|
|
|
+ pointCenter.dx - arrowProp.halfWidth,
|
|
|
+ nRect.bottom,
|
|
|
+ );
|
|
|
+
|
|
|
+ // 下面计算开始的圆弧
|
|
|
+ {
|
|
|
+ Offset pointStartArcBegin = Offset(
|
|
|
+ pointStart.dx + arrowRadius,
|
|
|
+ pointStart.dy,
|
|
|
+ );
|
|
|
+ Offset pointStartArcEnd = Offset(
|
|
|
+ pointStart.dx - arrowProp.projectionOnMain,
|
|
|
+ pointStart.dy + arrowProp.projectionOnCross,
|
|
|
+ );
|
|
|
+ path.lineTo(pointStartArcBegin.dx, pointStartArcBegin.dy);
|
|
|
+ path.quadraticBezierTo(
|
|
|
+ pointStart.dx,
|
|
|
+ pointStart.dy,
|
|
|
+ pointStartArcEnd.dx,
|
|
|
+ pointStartArcEnd.dy,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ // 计算中间箭头的圆弧
|
|
|
+ {
|
|
|
+ Offset pointArrowArcBegin = Offset(
|
|
|
+ pointArrow.dx + arrowProp.arrowProjectionOnMain,
|
|
|
+ pointArrow.dy - arrowProp.topLen,
|
|
|
+ );
|
|
|
+ Offset pointArrowArcEnd = Offset(
|
|
|
+ pointArrow.dx - arrowProp.arrowProjectionOnMain,
|
|
|
+ pointArrow.dy - arrowProp.topLen,
|
|
|
+ );
|
|
|
+ path.lineTo(pointArrowArcBegin.dx, pointArrowArcBegin.dy);
|
|
|
+ path.quadraticBezierTo(
|
|
|
+ pointArrow.dx,
|
|
|
+ pointArrow.dy,
|
|
|
+ pointArrowArcEnd.dx,
|
|
|
+ pointArrowArcEnd.dy,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ // 下面计算结束的圆弧
|
|
|
+ {
|
|
|
+ Offset pointEndArcBegin = Offset(
|
|
|
+ pointEnd.dx + arrowProp.projectionOnMain,
|
|
|
+ pointEnd.dy + arrowProp.projectionOnCross,
|
|
|
+ );
|
|
|
+ Offset pointEndArcEnd = Offset(pointEnd.dx - arrowRadius, pointEnd.dy);
|
|
|
+ path.lineTo(pointEndArcBegin.dx, pointEndArcBegin.dy);
|
|
|
+ path.quadraticBezierTo(
|
|
|
+ pointEnd.dx,
|
|
|
+ pointEnd.dy,
|
|
|
+ pointEndArcEnd.dx,
|
|
|
+ pointEndArcEnd.dy,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ path.lineTo(nRect.left + borderRadius.bottomLeft.x, nRect.bottom);
|
|
|
+ // bottomLeft radius
|
|
|
+ path.arcToPoint(
|
|
|
+ Offset(nRect.left, nRect.bottom - borderRadius.bottomRight.y),
|
|
|
+ radius: borderRadius.bottomLeft,
|
|
|
+ rotation: 90,
|
|
|
+ );
|
|
|
+
|
|
|
+ // 箭头在左边
|
|
|
+ if (arrowDirection == AxisDirection.left) {
|
|
|
+ Offset pointCenter = Offset(
|
|
|
+ nRect.left,
|
|
|
+ nRect.top + (arrowOffset ?? nRect.height / 2),
|
|
|
+ );
|
|
|
+ Offset pointStart = Offset(
|
|
|
+ nRect.left,
|
|
|
+ pointCenter.dy + arrowProp.halfWidth,
|
|
|
+ );
|
|
|
+ Offset pointArrow = Offset(rect.left, pointCenter.dy);
|
|
|
+ Offset pointEnd = Offset(
|
|
|
+ nRect.left,
|
|
|
+ pointCenter.dy - arrowProp.halfWidth,
|
|
|
+ );
|
|
|
+
|
|
|
+ // 下面计算开始的圆弧
|
|
|
+ {
|
|
|
+ Offset pointStartArcBegin = Offset(
|
|
|
+ pointStart.dx,
|
|
|
+ pointStart.dy + arrowRadius,
|
|
|
+ );
|
|
|
+ Offset pointStartArcEnd = Offset(
|
|
|
+ pointStart.dx - arrowProp.projectionOnCross,
|
|
|
+ pointStart.dy - arrowProp.projectionOnMain,
|
|
|
+ );
|
|
|
+ path.lineTo(pointStartArcBegin.dx, pointStartArcBegin.dy);
|
|
|
+ path.quadraticBezierTo(
|
|
|
+ pointStart.dx,
|
|
|
+ pointStart.dy,
|
|
|
+ pointStartArcEnd.dx,
|
|
|
+ pointStartArcEnd.dy,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ // 计算中间箭头的圆弧
|
|
|
+ {
|
|
|
+ Offset pointArrowArcBegin = Offset(
|
|
|
+ pointArrow.dx + arrowProp.topLen,
|
|
|
+ pointArrow.dy + arrowProp.arrowProjectionOnMain,
|
|
|
+ );
|
|
|
+ Offset pointArrowArcEnd = Offset(
|
|
|
+ pointArrow.dx + arrowProp.topLen,
|
|
|
+ pointArrow.dy - arrowProp.arrowProjectionOnMain,
|
|
|
+ );
|
|
|
+ path.lineTo(pointArrowArcBegin.dx, pointArrowArcBegin.dy);
|
|
|
+ path.quadraticBezierTo(
|
|
|
+ pointArrow.dx,
|
|
|
+ pointArrow.dy,
|
|
|
+ pointArrowArcEnd.dx,
|
|
|
+ pointArrowArcEnd.dy,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ // 下面计算结束的圆弧
|
|
|
+ {
|
|
|
+ Offset pointEndArcBegin = Offset(
|
|
|
+ pointEnd.dx - arrowProp.projectionOnCross,
|
|
|
+ pointEnd.dy + arrowProp.projectionOnMain,
|
|
|
+ );
|
|
|
+ Offset pointEndArcEnd = Offset(pointEnd.dx, pointEnd.dy - arrowRadius);
|
|
|
+ path.lineTo(pointEndArcBegin.dx, pointEndArcBegin.dy);
|
|
|
+ path.quadraticBezierTo(
|
|
|
+ pointEnd.dx,
|
|
|
+ pointEnd.dy,
|
|
|
+ pointEndArcEnd.dx,
|
|
|
+ pointEndArcEnd.dy,
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ path.lineTo(nRect.left, nRect.top + borderRadius.topLeft.y);
|
|
|
+ path.arcToPoint(startPoint, radius: borderRadius.topLeft, rotation: 90);
|
|
|
+
|
|
|
+ return path;
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {
|
|
|
+ if (fillColor == null && side == BorderSide.none) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ final path = _buildPath(rect);
|
|
|
+ final Paint paint =
|
|
|
+ Paint()
|
|
|
+ ..color = side.color
|
|
|
+ ..style = PaintingStyle.stroke;
|
|
|
+ if (fillColor != null) {
|
|
|
+ paint.color = fillColor!;
|
|
|
+ paint.style = PaintingStyle.fill;
|
|
|
+ canvas.drawPath(path, paint);
|
|
|
+ }
|
|
|
+ if (side != BorderSide.none) {
|
|
|
+ paint.color = side.color;
|
|
|
+ paint.strokeWidth = side.width;
|
|
|
+ paint.style = PaintingStyle.stroke;
|
|
|
+ canvas.drawPath(path, paint);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ ShapeBorder scale(double t) {
|
|
|
+ return BubbleShapeBorder(
|
|
|
+ arrowDirection: arrowDirection,
|
|
|
+ side: side.scale(t),
|
|
|
+ borderRadius: borderRadius * t,
|
|
|
+ arrowLength: arrowLength * t,
|
|
|
+ arrowWidth: arrowWidth * t,
|
|
|
+ arrowRadius: arrowRadius * t,
|
|
|
+ arrowOffset: (arrowOffset ?? 0) * t,
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ bool operator ==(Object other) {
|
|
|
+ if (other.runtimeType != runtimeType) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return other is BubbleShapeBorder &&
|
|
|
+ other.side == side &&
|
|
|
+ other.borderRadius == borderRadius &&
|
|
|
+ other.arrowLength == arrowLength &&
|
|
|
+ other.arrowWidth == arrowWidth &&
|
|
|
+ other.arrowRadius == arrowRadius &&
|
|
|
+ other.arrowDirection == arrowDirection &&
|
|
|
+ other.arrowOffset == arrowOffset &&
|
|
|
+ other.fillColor == fillColor;
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ int get hashCode => Object.hash(
|
|
|
+ side,
|
|
|
+ borderRadius,
|
|
|
+ arrowLength,
|
|
|
+ arrowWidth,
|
|
|
+ arrowRadius,
|
|
|
+ arrowDirection,
|
|
|
+ arrowOffset,
|
|
|
+ fillColor,
|
|
|
+ );
|
|
|
+}
|