前言
之前做业务的时候,一直依赖依赖库来实现虚线,今天学习了一下,才得知按钮也可以实现自定义虚线边框
思路
我们以ElevatedButton按钮为例,style下的ButtonStyle中,存在shape属性字段,支持一个OutlinedBorder类型的边框类,从下面可以了解到paint绘制方法,这个是绘制虚线的重点方法
自定义虚线边框组件
import 'dart:ui';
import 'package:flutter/material.dart';
class CustomDashBorder extends OutlinedBorder{
final double? dashGap;
final BorderRadiusGeometry? borderRadius;
CustomDashBorder({super.side,this.dashGap,this.borderRadius});
@override
OutlinedBorder copyWith({BorderSide? side,double? dashGap,BorderRadiusGeometry? borderRadius}) {
return CustomDashBorder(side: side ?? this.side,dashGap: dashGap ?? this.dashGap,borderRadius: borderRadius ?? this.borderRadius);
}
@override
Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
var innerRRect = borderRadius!.resolve(textDirection).toRRect(rect);
var adjustedRRect = innerRRect.deflate(side.strokeInset);
return Path()..addRRect(adjustedRRect);
}
@override
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
return Path()..addRRect(borderRadius!.resolve(textDirection).toRRect(rect));
}
@override
void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {
final Paint paint = Paint()
..color = side.color
..strokeWidth = side.width
..style = PaintingStyle.stroke;
// TODO: implement paint
var path = getOuterPath(rect);
CustomDashBorderPaint(dashGap ?? 3).paint(canvas, path, paint);
}
@override
ShapeBorder scale(double t) {
return CustomDashBorder(
side: side.scale(t),
dashGap: dashGap! * t,
);
}
@override
ShapeBorder? lerpTo(ShapeBorder? b, double t) {
if (b is CustomDashBorder) {
return CustomDashBorder(
side: BorderSide.lerp(side, b.side, t),
borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t)!,
dashGap: lerpDouble(dashGap, b.dashGap, t) ?? 0,
);
}
return super.lerpTo(b, t);
}
@override
ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
if (a is CustomDashBorder) {
return CustomDashBorder(
side: BorderSide.lerp(a.side, side, t),
borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t)!,
dashGap: lerpDouble(a.dashGap, dashGap, t) ?? 0,
);
}
return super.lerpFrom(a, t);
}
}
class CustomDashBorderPaint {
final double dashGap;
CustomDashBorderPaint(this.dashGap);
void paint(Canvas canvas,Path path,Paint paint){
var pms = path.computeMetrics();
final double partLength = dashGap + dashGap;
for(var pm in pms) {
int count = pm.length ~/ partLength;
for (var i = 0; i < count; i++) {
canvas.drawPath(
pm.extractPath(partLength * i, partLength * i + dashGap), paint);
}
var sub = pm.length % partLength;
canvas.drawPath(pm.extractPath(pm.length - sub, pm.length), paint);
}
}
}
先简单介绍下复写的几个方法
- copyWith (就是复制一个属性值一样的类)
- getInnerPath (获取当前矩形内部的空间 如果width = 10 则内层空间只有一半5)
- getOuterPath (获取当前可视化矩形完整的显示RRect矩形路径)
- paint (绘制方法)
- scale (根据当前边框进行缩放)
- lerpTo 和 lerpFrom(动画过渡)
自定义绘制方法
可以看到我们自己定义了一个CustomDashBorderPaint类,其中提供了dashGap 实际上可以分开为 step(实现) / span(空白) 我们为了方便则统一值
var pms = path.computeMetrics(); 用来分成当前边框一个个连续的路径段落
final double partLength = dashGap + dashGap; (可认为 step + span)for(var pm in pms) { //获取当前路径段落总长度整除实线和空白的总和 int count = pm.length ~/ partLength; for (var i = 0; i < count; i++) { // extractPath 用来裁切段落 这里规律假设pm.length = 30 //step = 5 span = 3 // 30 ~ 8 // 0 - > 5 i = 0 // 8 -> 13 i= 1 // 16 -> 21 i = 2 // 24 -> 29 + i = 3 canvas.drawPath( pm.extractPath(partLength * i, partLength * i + dashGap), paint); } //处理尾巴不够整除的绘制逻辑 var sub = pm.length % partLength; canvas.drawPath(pm.extractPath(pm.length - sub, pm.length), paint); }