Flutter各种虚线实战和虚线边框原理

简介

Flutter中如何使用虚线
在这里插入图片描述
使用的是第三方库
Flutter-Dotted-Border其中的封装了基本的图形样式,矩形、圆角矩形、圆形、椭圆形,以及自定义虚线框。内部其实是使用了
path_drawing
这个库作为绘制虚线。

Flutter-Dotted-Border

其中就只有两个文件dotted_border.dart和dash_printer.dart

基本使用

Widget get rectBorderWidget {
    return DottedBorder(
      dashPattern: [8, 4],
      strokeWidth: 2,
      padding: EdgeInsets.all(0),
      child: Container(
        height: 200,
        width: 120,
        color: Colors.red,
        // color: Colors.black26,
      ),
    );
  }

dashPattern: [8, 4],第一个参数表示虚线长度,第二参数表示空格长度。

DottedBorder虚线边框封装核心代码分析

class DottedBorder extends StatelessWidget {
  final Widget child;
  final EdgeInsets padding;
  final double strokeWidth;
  final Color color;
  final List<double> dashPattern;
  final BorderType borderType;
  final Radius radius;
  final PathBuilder customPath;
  final StrokeCap strokeCap;

  DottedBorder({
    @required this.child,
    this.color = Colors.black,
    this.strokeWidth = 1,
    this.borderType = BorderType.Rect,
    this.dashPattern = const <double>[3, 1],
    this.padding = const EdgeInsets.all(2),
    this.radius = const Radius.circular(0),
    this.customPath,
    this.strokeCap = StrokeCap.butt,
  }) {
    assert(child != null);
    assert(_isValidDashPattern(dashPattern), 'Invalid dash pattern');
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        Positioned.fill(
          child: CustomPaint(
            painter: _DashPainter(
              strokeWidth: strokeWidth,
              radius: radius,
              color: color,
              borderType: borderType,
              dashPattern: dashPattern,
              customPath: customPath,
              strokeCap: strokeCap,
            ),
          ),
        ),
        Padding(
          padding: padding,
          child: child,
        ),
      ],
    );
  }
}

最外层使用了Stack,将两个组件组合在一起,Positioned.fill()用来控制CustomPaint中的宽高。这样做的好处,可以让child的视图显示在CustomPaint绘制的上层

思考

如果不使用Stack,而是直接使用CustomPaint中的child参数传递子组件,会是什么效果?

		CustomPaint(
            painter: _DashPainter(
              strokeWidth: strokeWidth,
              radius: radius,
              color: color,
              borderType: borderType,
              dashPattern: dashPattern,
              customPath: customPath,
              strokeCap: strokeCap,
            ),
            child: child,
          ),
        ),

答:这样做会导致,CustomPaint绘制在外层,遮挡住child的内容。

_DashPainter封装边框的类

class _DashPainter extends CustomPainter {

  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint()
      ..strokeWidth = strokeWidth
      ..color = color
      ..strokeCap = strokeCap
      ..style = PaintingStyle.stroke;

    Path _path;
    // 关键在下面,获取虚线的Path
    if (customPath != null) {
      _path = dashPath(
        customPath(size),
        dashArray: CircularIntervalList(dashPattern),
      );
    } else {
      _path = _getPath(size);
    }

	// 将结果绘制出来
    canvas.drawPath(_path, paint);
  }
}

获取虚线path的方法

Path _getPath(Size size) {
    Path path;
    switch (borderType) {
    	...
      case BorderType.Rect:
        path = _getRectPath(size);
        break;
        ...
    }

    return dashPath(path, dashArray: CircularIntervalList(dashPattern));
  }
Path _getRectPath(Size size) {
    return Path()
      ..addRect(
        Rect.fromLTWH(
          0,
          0,
          size.width,
          size.height,
        ),
      );
  }

其实就是根据边框类型,先绘制了一个实线的边框,关键是调用dashPath(path, dashArray: CircularIntervalList(dashPattern)); 得到虚线边框。

绘制虚线的核心库path_drawing

在这里插入图片描述
path_drawing.dart主入口文件

export 'package:path_drawing/src/parse_path.dart';
export 'package:path_drawing/src/dash_path.dart';
export 'package:path_drawing/src/trim_path.dart';

其实就只有3个有用的文件

  1. dash_path绘制虚线
  2. parse_path解析svg成path
  3. trim_path裁剪path

这里只分析dash_path.dart,绘制虚线的类。

dash_path.dart绘制虚线核心类

Path dashPath(
  Path source, {
  @required CircularIntervalList<double> dashArray,
  DashOffset dashOffset,
}) {
  assert(dashArray != null);
  if (source == null) {
    return null;
  }

	// 默认从path的起点开始绘制
  dashOffset = dashOffset ?? const DashOffset.absolute(0.0);
  // TODO: Is there some way to determine how much of a path would be visible today?

  final Path dest = Path();
  // 计算完整的线段的长度
  for (final PathMetric metric in source.computeMetrics()) {
    double distance = dashOffset._calculate(metric.length);
    // draw用来取反,为true表示绘制该线段,false表示不绘制
    bool draw = true;
    while (distance < metric.length) {
    // dashArray其实就是dashPattern: [8, 4]的值
      final double len = dashArray.next;
      if (draw) {
      	// 不断的调整偏移量,绘制线段
        dest.addPath(metric.extractPath(distance, distance + len), Offset.zero);
      }
      distance += len;
      draw = !draw;
    }
  }

  return dest;
}
  1. dashOffset获取开始绘制虚线的偏移量,默认0,也就是从头开始绘制。
  2. 先计算边框的path总长度。
  3. 根据dashArray来获取虚线、空格的数据。
  4. 获取总path的偏移量,也就是小线段(虚线)。
  5. 通过draw控制绘制间隔。
  6. 返回最终的dest,就是通过偏移、绘制小线段形成的虚线。

CustomPaint的size属性研究

有size或child

CustomPaint(
      // size: Size(200.0, 200.0),
      painter: TestPainter(),
      child: Container(
        padding: EdgeInsets.all(0),
        height: 200,
        width: 120,
        color: Colors.black26,
        child: Text("内部", style: TextStyle(color: Colors.redAccent),),
      ),
    );
/// The size that this [CustomPaint] should aim for, given the layout
  /// constraints, if there is no child.
  ///
  /// Defaults to [Size.zero].
  ///
  /// If there's a child, this is ignored, and the size of the child is used
  /// instead.
  final Size size;

size属性,文档已经说明了。默认是0,如果设置了child,则size属性无效,以child宽高为准。

没有size和child

如果是没有给定size,也没有给child。则依然可以根据TestPainter绘制出图形,但这个图形没事实际的宽高,也就是说在布局的时候无法提供一个宽高值给其他组件参考,

class PaintView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    
    return CustomPaint(
      painter: TestPainter(),
 
    );
  }
}
    
class TestPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    print("打印size.width = ${size.width} size.height = ${size.height}");
    Paint paint = Paint()
      ..strokeWidth = 2
      ..color = Colors.black
      ..strokeCap = StrokeCap.round
      ..style = PaintingStyle.stroke;
    
    Path path = Path()
      ..addRect(
        Rect.fromLTWH(
          0,
          0,
          40,
          200,
        ),
      );

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}
		Center(
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              // padding: const EdgeInsets.all(0.0),
              child: Wrap(
                alignment: WrapAlignment.center,
                crossAxisAlignment: WrapCrossAlignment.center,
                spacing: 8,
                // spacing: 0,
                children: <Widget>[
                  PaintView(),
                  Text("测试布局位置"),
                ],
              ),
            ),
          ),

在这里插入图片描述
结果可以看到,没有给size也没有给child,则实际PaintView是没有宽高的,但依然能绘制出图形,这会造成Text(“测试布局位置”)的布局无法定位在矩形的右侧。在使用的过程一定要注意!

总结

Flutter-Dotted-Border 封装了基本的虚线边框。

path_drawing
实际绘制虚线的核心库,本质是利用了path的长度,计算每一个小线段的偏移量,将多个小线段合并成一个完整的虚线边框。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值