使用方式跟 BoxDecoration 一致,是根据BoxDecoration修改来的,新增四个参数:
this.strokeWidth = 1.0,// 虚线高度 this.gap = 5.0,//虚线的点的长度和间隔 this.dashedColor,//虚线颜色 this.dawDashed = true,//是否画虚线,默认为画虚线不画边框
需要注意的是,虚线和边框不能共存,如果不画虚线,dawDashed设为 false。
使用:
decoration: const DashedDecoration(
dashedColor: Colors.yellow,
borderRadius: const BorderRadius.all(Radius.circular(4.0)),
),
代码:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:math' as math;
import 'package:saler_incoming/utils/LogUtils.dart';
class DashedDecoration extends Decoration {
/// Creates a box decoration.
///
/// * If [color] is null, this decoration does not paint a background color.
/// * If [image] is null, this decoration does not paint a background image.
/// * If [border] is null, this decoration does not paint a border.
/// * If [borderRadius] is null, this decoration uses more efficient background
/// painting commands. The [borderRadius] argument must be null if [shape] is
/// [BoxShape.circle].
/// * If [boxShadow] is null, this decoration does not paint a shadow.
/// * If [gradient] is null, this decoration does not paint gradients.
/// * If [backgroundBlendMode] is null, this decoration paints with [BlendMode.srcOver]
///
/// The [shape] argument must not be null.
const DashedDecoration({
this.color,
this.image,
this.border,
this.borderRadius,
this.boxShadow,
this.gradient,
this.backgroundBlendMode,
this.shape = BoxShape.rectangle,
this.strokeHeight = 1.0,
this.gap = 5.0,
this.dashedColor,
this.dawDashed = true,
}) : assert(shape != null),
assert(
backgroundBlendMode == null || color != null || gradient != null,
"backgroundBlendMode applies to DashedDecoration's background color or "
'gradient, but no color or gradient was provided.');
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
DashedDecoration copyWith({
Color color,
DecorationImage image,
BoxBorder border,
BorderRadiusGeometry borderRadius,
List<BoxShadow> boxShadow,
Gradient gradient,
BlendMode backgroundBlendMode,
BoxShape shape,
}) {
return DashedDecoration(
color: color ?? this.color,
image: image ?? this.image,
border: border ?? this.border,
borderRadius: borderRadius ?? this.borderRadius,
boxShadow: boxShadow ?? this.boxShadow,
gradient: gradient ?? this.gradient,
backgroundBlendMode: backgroundBlendMode ?? this.backgroundBlendMode,
shape: shape ?? this.shape,
);
}
@override
bool debugAssertIsValid() {
assert(shape != BoxShape.circle ||
borderRadius == null); // Can't have a border radius if you're a circle.
return super.debugAssertIsValid();
}
/// The color to fill in the background of the box.
///
/// The color is filled into the [shape] of the box (e.g., either a rectangle,
/// potentially with a [borderRadius], or a circle).
///
/// This is ignored if [gradient] is non-null.
///
/// The [color] is drawn under the [image].
final Color color;
/// An image to paint above the background [color] or [gradient].
///
/// If [shape] is [BoxShape.circle] then the image is clipped to the circle's
/// boundary; if [borderRadius] is non-null then the image is clipped to the
/// given radii.
final DecorationImage image;
/// A border to draw above the background [color], [gradient], or [image].
///
/// Follows the [shape] and [borderRadius].
///
/// Use [Border] objects to describe borders that do not depend on the reading
/// direction.
///
/// Use [BoxBorder] objects to describe borders that should flip their left
/// and right edges based on whether the text is being read left-to-right or
/// right-to-left.
final BoxBorder border;
/// If non-null, the corners of this box are rounded by this [BorderRadius].
///
/// Applies only to boxes with rectangular shapes; ignored if [shape] is not
/// [BoxShape.rectangle].
///
/// {@macro flutter.painting.boxDecoration.clip}
final BorderRadiusGeometry borderRadius;
/// A list of shadows cast by this box behind the box.
///
/// The shadow follows the [shape] of the box.
///
/// See also:
///
/// * [kElevationToShadow], for some predefined shadows used in Material
/// Design.
/// * [PhysicalModel], a widget for showing shadows.
final List<BoxShadow> boxShadow;
/// A gradient to use when filling the box.
///
/// If this is specified, [color] has no effect.
///
/// The [gradient] is drawn under the [image].
final Gradient gradient;
/// The blend mode applied to the [color] or [gradient] background of the box.
///
/// If no [backgroundBlendMode] is provided then the default painting blend
/// mode is used.
///
/// If no [color] or [gradient] is provided then the blend mode has no impact.
final BlendMode backgroundBlendMode;
/// The shape to fill the background [color], [gradient], and [image] into and
/// to cast as the [boxShadow].
///
/// If this is [BoxShape.circle] then [borderRadius] is ignored.
///
/// The [shape] cannot be interpolated; animating between two [DashedDecoration]s
/// with different [shape]s will result in a discontinuity in the rendering.
/// To interpolate between two shapes, consider using [ShapeDecoration] and
/// different [ShapeBorder]s; in particular, [CircleBorder] instead of
/// [BoxShape.circle] and [RoundedRectangleBorder] instead of
/// [BoxShape.rectangle].
///
/// {@macro flutter.painting.boxDecoration.clip}
final BoxShape shape;
final double strokeHeight;
final double gap;
final Color dashedColor;
final bool dawDashed;
@override
EdgeInsetsGeometry get padding => border?.dimensions;
@override
Path getClipPath(Rect rect, TextDirection textDirection) {
Path clipPath;
switch (shape) {
case BoxShape.circle:
clipPath = Path()..addOval(rect);
break;
case BoxShape.rectangle:
if (borderRadius != null)
clipPath = Path()
..addRRect(borderRadius.resolve(textDirection).toRRect(rect));
break;
}
return clipPath;
}
/// Returns a new box decoration that is scaled by the given factor.
DashedDecoration scale(double factor) {
return DashedDecoration(
color: Color.lerp(null, color, factor),
image: image,
// TODO(ianh): fade the image from transparent
border: BoxBorder.lerp(null, border, factor),
borderRadius: BorderRadiusGeometry.lerp(null, borderRadius, factor),
boxShadow: BoxShadow.lerpList(null, boxShadow, factor),
gradient: gradient?.scale(factor),
shape: shape,
);
}
@override
bool get isComplex => boxShadow != null;
@override
DashedDecoration lerpFrom(Decoration a, double t) {
if (a == null) return scale(t);
if (a is DashedDecoration) return DashedDecoration.lerp(a, this, t);
return super.lerpFrom(a, t) as DashedDecoration;
}
@override
DashedDecoration lerpTo(Decoration b, double t) {
if (b == null) return scale(1.0 - t);
if (b is DashedDecoration) return DashedDecoration.lerp(this, b, t);
return super.lerpTo(b, t) as DashedDecoration;
}
/// Linearly interpolate between two box decorations.
///
/// Interpolates each parameter of the box decoration separately.
///
/// The [shape] is not interpolated. To interpolate the shape, consider using
/// a [ShapeDecoration] with different border shapes.
///
/// If both values are null, this returns null. Otherwise, it returns a
/// non-null value. If one of the values is null, then the result is obtained
/// by applying [scale] to the other value. If neither value is null and `t ==
/// 0.0`, then `a` is returned unmodified; if `t == 1.0` then `b` is returned
/// unmodified. Otherwise, the values are computed by interpolating the
/// properties appropriately.
///
/// {@macro dart.ui.shadow.lerp}
///
/// See also:
///
/// * [Decoration.lerp], which can interpolate between any two types of
/// [Decoration]s, not just [DashedDecoration]s.
/// * [lerpFrom] and [lerpTo], which are used to implement [Decoration.lerp]
/// and which use [DashedDecoration.lerp] when interpolating two
/// [DashedDecoration]s or a [DashedDecoration] to or from null.
static DashedDecoration lerp(
DashedDecoration a, DashedDecoration b, double t) {
assert(t != null);
if (a == null && b == null) return null;
if (a == null) return b.scale(t);
if (b == null) return a.scale(1.0 - t);
if (t == 0.0) return a;
if (t == 1.0) return b;
return DashedDecoration(
color: Color.lerp(a.color, b.color, t),
image: t < 0.5 ? a.image : b.image,
// TODO(ianh): cross-fade the image
border: BoxBorder.lerp(a.border, b.border, t),
borderRadius:
BorderRadiusGeometry.lerp(a.borderRadius, b.borderRadius, t),
boxShadow: BoxShadow.lerpList(a.boxShadow, b.boxShadow, t),
gradient: Gradient.lerp(a.gradient, b.gradient, t),
shape: t < 0.5 ? a.shape : b.shape,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other.runtimeType != runtimeType) return false;
return other is DashedDecoration &&
other.color == color &&
other.image == image &&
other.border == border &&
other.borderRadius == borderRadius &&
other.boxShadow == boxShadow &&
other.gradient == gradient &&
other.shape == shape;
}
@override
int get hashCode {
return hashValues(
color,
image,
border,
borderRadius,
boxShadow,
gradient,
shape,
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.whitespace
..emptyBodyDescription = '<no decorations specified>';
properties.add(ColorProperty('color', color, defaultValue: null));
properties.add(DiagnosticsProperty<DecorationImage>('image', image,
defaultValue: null));
properties.add(
DiagnosticsProperty<BoxBorder>('border', border, defaultValue: null));
properties.add(DiagnosticsProperty<BorderRadiusGeometry>(
'borderRadius', borderRadius,
defaultValue: null));
properties.add(IterableProperty<BoxShadow>('boxShadow', boxShadow,
defaultValue: null, style: DiagnosticsTreeStyle.whitespace));
properties.add(DiagnosticsProperty<Gradient>('gradient', gradient,
defaultValue: null));
properties.add(EnumProperty<BoxShape>('shape', shape,
defaultValue: BoxShape.rectangle));
}
@override
bool hitTest(Size size, Offset position, {TextDirection textDirection}) {
assert(shape != null);
assert((Offset.zero & size).contains(position));
switch (shape) {
case BoxShape.rectangle:
if (borderRadius != null) {
final RRect bounds =
borderRadius.resolve(textDirection).toRRect(Offset.zero & size);
return bounds.contains(position);
}
return true;
case BoxShape.circle:
// Circles are inscribed into our smallest dimension.
final Offset center = size.center(Offset.zero);
final double distance = (position - center).distance;
return distance <= math.min(size.width, size.height) / 2.0;
}
assert(shape != null);
return null;
}
@override
_BoxDecorationPainter createBoxPainter([VoidCallback onChanged]) {
assert(onChanged != null || image == null);
return _BoxDecorationPainter(this, onChanged);
}
}
/// An object that paints a [DashedDecoration] into a canvas.
class _BoxDecorationPainter extends BoxPainter {
_BoxDecorationPainter(this._decoration, VoidCallback onChanged)
: assert(_decoration != null),
super(onChanged);
final DashedDecoration _decoration;
Paint _cachedBackgroundPaint;
Rect _rectForCachedBackgroundPaint;
Paint _getBackgroundPaint(Rect rect, TextDirection textDirection) {
assert(rect != null);
assert(
_decoration.gradient != null || _rectForCachedBackgroundPaint == null);
if (_cachedBackgroundPaint == null ||
(_decoration.gradient != null &&
_rectForCachedBackgroundPaint != rect)) {
final Paint paint = Paint();
if (_decoration.backgroundBlendMode != null)
paint.blendMode = _decoration.backgroundBlendMode;
if (_decoration.color != null) paint.color = _decoration.color;
if (_decoration.gradient != null) {
paint.shader = _decoration.gradient
.createShader(rect, textDirection: textDirection);
_rectForCachedBackgroundPaint = rect;
}
_cachedBackgroundPaint = paint;
}
return _cachedBackgroundPaint;
}
void _paintBox(
Canvas canvas, Rect rect, Paint paint, TextDirection textDirection) {
switch (_decoration.shape) {
case BoxShape.circle:
assert(_decoration.borderRadius == null);
final Offset center = rect.center;
final double radius = rect.shortestSide / 2.0;
canvas.drawCircle(center, radius, paint);
break;
case BoxShape.rectangle:
if (_decoration.borderRadius == null) {
canvas.drawRect(rect, paint);
} else {
canvas.drawRRect(
_decoration.borderRadius.resolve(textDirection).toRRect(rect),
paint);
}
break;
}
}
void _paintShadows(Canvas canvas, Rect rect, TextDirection textDirection) {
if (_decoration.boxShadow == null) return;
for (final BoxShadow boxShadow in _decoration.boxShadow) {
final Paint paint = boxShadow.toPaint();
final Rect bounds =
rect.shift(boxShadow.offset).inflate(boxShadow.spreadRadius);
_paintBox(canvas, bounds, paint, textDirection);
}
}
void _paintBackgroundColor(
Canvas canvas, Rect rect, TextDirection textDirection) {
if (_decoration.color != null || _decoration.gradient != null)
_paintBox(canvas, rect, _getBackgroundPaint(rect, textDirection),
textDirection);
}
DecorationImagePainter _imagePainter;
void _paintBackgroundImage(
Canvas canvas, Rect rect, ImageConfiguration configuration) {
if (_decoration.image == null) return;
_imagePainter ??= _decoration.image.createPainter(onChanged);
Path clipPath;
switch (_decoration.shape) {
case BoxShape.circle:
clipPath = Path()..addOval(rect);
break;
case BoxShape.rectangle:
if (_decoration.borderRadius != null)
clipPath = Path()
..addRRect(_decoration.borderRadius
.resolve(configuration.textDirection)
.toRRect(rect));
break;
}
_imagePainter.paint(canvas, rect, clipPath, configuration);
}
@override
void dispose() {
_imagePainter?.dispose();
super.dispose();
}
/// Paint the box decoration into the given location on the given canvas
@override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
assert(configuration != null);
assert(configuration.size != null);
final Rect rect = offset & configuration.size;
final TextDirection textDirection = configuration.textDirection;
_paintShadows(canvas, rect, textDirection);
_paintBackgroundColor(canvas, rect, textDirection);
_paintBackgroundImage(canvas, rect, configuration);
if (_decoration.dawDashed != null && !_decoration.dawDashed) {
_decoration.border?.paint(
canvas,
rect,
shape: _decoration.shape,
borderRadius: _decoration.borderRadius as BorderRadius,
textDirection: configuration.textDirection,
);
return;
}
Paint dashedPaint = Paint()
..color = _decoration.dashedColor
..strokeWidth = _decoration.strokeHeight
..style = PaintingStyle.stroke;
Path _topPath = getDashedPath(
a: math.Point(rect.topLeft.dx, rect.topLeft.dy),
b: math.Point(rect.topRight.dx, rect.topRight.dy),
gap: _decoration.gap,
);
Path _rightPath = getDashedPath(
a: math.Point(rect.topRight.dx, rect.topRight.dy),
b: math.Point(rect.bottomRight.dx, rect.bottomRight.dy),
gap: _decoration.gap,
);
Path _bottomPath = getDashedPath(
a: math.Point(rect.bottomLeft.dx, rect.bottomLeft.dy),
b: math.Point(rect.bottomRight.dx, rect.bottomRight.dy),
gap: _decoration.gap,
);
Path _leftPath = getDashedPath(
a: math.Point(rect.topLeft.dx, rect.topLeft.dy),
b: math.Point(rect.bottomLeft.dx, rect.bottomLeft.dy),
gap: _decoration.gap,
);
canvas.drawPath(_topPath, dashedPaint);
canvas.drawPath(_rightPath, dashedPaint);
canvas.drawPath(_bottomPath, dashedPaint);
canvas.drawPath(_leftPath, dashedPaint);
// }
}
Path getDashedPath({
@required math.Point<double> a,
@required math.Point<double> b,
@required gap,
}) {
Size size = Size(b.x - a.x, b.y - a.y);
Path path = Path();
path.moveTo(a.x, a.y);
bool shouldDraw = true;
math.Point currentPoint = math.Point(a.x, a.y);
num radians = math.atan(size.height / size.width);
num dx = math.cos(radians) * gap < 0
? math.cos(radians) * gap * -1
: math.cos(radians) * gap;
num dy = math.sin(radians) * gap < 0
? math.sin(radians) * gap * -1
: math.sin(radians) * gap;
while (currentPoint.x <= b.x && currentPoint.y <= b.y) {
shouldDraw
? path.lineTo(currentPoint.x, currentPoint.y)
: path.moveTo(currentPoint.x, currentPoint.y);
shouldDraw = !shouldDraw;
currentPoint = math.Point(
currentPoint.x + dx,
currentPoint.y + dy,
);
}
return path;
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
@override
String toString() {
return 'BoxPainter for $_decoration';
}
}
关键代码:
// 画笔
Paint dashedPaint = Paint()
..color = _decoration.dashedColor
..strokeWidth = _decoration.strokeHeight
..style = PaintingStyle.stroke;
// 顶部线
Path _topPath = getDashedPath(
a: math.Point(rect.topLeft.dx, rect.topLeft.dy),
b: math.Point(rect.topRight.dx, rect.topRight.dy),
gap: _decoration.gap,
);
// 右边线
Path _rightPath = getDashedPath(
a: math.Point(rect.topRight.dx, rect.topRight.dy),
b: math.Point(rect.bottomRight.dx, rect.bottomRight.dy),
gap: _decoration.gap,
);
// 下边的线
Path _bottomPath = getDashedPath(
a: math.Point(rect.bottomLeft.dx, rect.bottomLeft.dy),
b: math.Point(rect.bottomRight.dx, rect.bottomRight.dy),
gap: _decoration.gap,
);
// 左边线
Path _leftPath = getDashedPath(
a: math.Point(rect.topLeft.dx, rect.topLeft.dy),
b: math.Point(rect.bottomLeft.dx, rect.bottomLeft.dy),
gap: _decoration.gap,
);
canvas.drawPath(_topPath, dashedPaint);
canvas.drawPath(_rightPath, dashedPaint);
canvas.drawPath(_bottomPath, dashedPaint);
canvas.drawPath(_leftPath, dashedPaint);

本文介绍了在Flutter中创建虚线边框的方法,重点在于BoxDecoration的使用,并且强调了虚线与实线边框不能同时存在。通过示例代码展示了如何设置虚线边框。

1万+

被折叠的 条评论
为什么被折叠?



