如何在 Flutter 中添加破折号
How to add line dash in Flutter
如何在 Flutter 中制作一个破折号?
As a workaround,对于你的情况,你可以这样做
class MySeparator extends StatelessWidget {
const MySeparator({Key? key, this.height = 1, this.color = Colors.black})
: super(key: key);
final double height;
final Color color;
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final boxWidth = constraints.constrainWidth();
const dashWidth = 10.0;
final dashHeight = height;
final dashCount = (boxWidth / (2 * dashWidth)).floor();
return Flex(
children: List.generate(dashCount, (_) {
return SizedBox(
width: dashWidth,
height: dashHeight,
child: DecoratedBox(
decoration: BoxDecoration(color: color),
),
);
}),
mainAxisAlignment: MainAxisAlignment.spaceBetween,
direction: Axis.horizontal,
);
},
);
}
}
并使用它const MySeparator()
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Material(
child: Container(
color: Colors.blue,
child: Center(
child: Container(
height: 600,
width: 350,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(16.0)),
),
child: Flex(
direction: Axis.vertical,
children: [
Expanded(child: Container()),
const MySeparator(color: Colors.grey),
Container(height: 200),
],
),
),
),
),
),
);
}
}
CustomPainter 在这里也可以提供帮助。在这个例子中是一条垂直虚线,但可以很容易地改变。
class LineDashedPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
var paint = Paint()..strokeWidth = 2;
var max = 35;
var dashWidth = 5;
var dashSpace = 5;
double startY = 0;
while (max >= 0) {
canvas.drawLine(Offset(0, startY), Offset(0, startY + dashWidth), paint);
final space = (dashSpace + dashWidth);
startY += space;
max -= space;
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
并且使用 CustomPaint Widget:
CustomPaint(painter: LineDashedPainter())
创建这个 class:
class DotWidget extends StatelessWidget {
final double totalWidth, dashWidth, emptyWidth, dashHeight;
final Color dashColor;
const DotWidget({
this.totalWidth = 300,
this.dashWidth = 10,
this.emptyWidth = 5,
this.dashHeight = 2,
this.dashColor = Colors.black,
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(
totalWidth ~/ (dashWidth + emptyWidth),
(_) => Container(
width: dashWidth,
height: dashHeight,
color: dashColor,
margin: EdgeInsets.only(left: emptyWidth / 2, right: emptyWidth / 2),
),
),
);
}
}
用法:
像使用任何其他小部件一样使用它
child: DotWidget(
dashColor: Colors.black,
dashHeight: 2,
dashWidth: 100,
)
这是水平虚线的代码,就像您的图片一样。 CustomPaint 是 flutter 团队强烈推荐的。渲染也快速高效。您可以使用 Offset 来改变方向。
class MyClass extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: CustomPaint(
painter: MyLinePainter(),
),
);
}
}
class MyLinePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
var max = 100;
var dashWidth, dashSpace = 5;
double startX = 0;
final paint = Paint()..color = Colors.grey;
while (max >= 0) {
canvas.drawLine(Offset(startX, 0), Offset(startX + dashWidth, 0), paint..strokeWidth = 1);
final space = (dashSpace + dashWidth);
startX += space;
max -= space;
}
}
我已经编写了 flutter_dash 库来绘制那个破折号。只有一行,你应该有一个破折号 :D
Dash(length: 200, dashColor: Colors.red)
试试吧!
试试这个,
class DotDivider extends StatelessWidget {
final double width;
final double height;
final double gap;
final Color color;
final double lineHeight;
const DotDivider(
{this.height = 1.0,
this.color = Colors.black,
this.width = 2.0,
this.gap = 2.0,
this.lineHeight = 10.0});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final boxWidth = constraints.constrainWidth();
final dashWidth = width;
final dashHeight = height;
final dashCount = (boxWidth / dashWidth).floor();
return Container(
height: (lineHeight * 2) + height,
child: ListView.builder(
physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
itemCount: dashCount,
itemBuilder: (BuildContext context, int index) => Center(
child: Container(
width: dashWidth,
height: dashHeight,
margin:
EdgeInsets.symmetric(vertical: lineHeight, horizontal: gap),
decoration: BoxDecoration(color: color),
),
),
),
);
},
);
}
}
class DashedLinePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
double dashWidth = 9, dashSpace = 5, startX = 0;
final paint = Paint()
..color = Colors.grey
..strokeWidth = 1;
while (startX < size.width) {
canvas.drawLine(Offset(startX, 0), Offset(startX + dashWidth, 0), paint);
startX += dashWidth + dashSpace;
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
// garis putus putus
Row(
children: List.generate(150~/10, (index) => Expanded(
child: Container(
color: index%2==0?Colors.transparent
:Colors.grey,
height: 2,
),
)),
),
垂直虚线:
我修改了 maksimr 的例子:
class DashedLine extends StatelessWidget {
final double height;
final double heightContainer;
final Color color;
const DashedLine({this.height = 3, this.color = Colors.black, this.heightContainer = 70});
@override
Widget build(BuildContext context) {
return Container(
height: heightContainer,
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final boxHeight = constraints.constrainHeight();
final dashWidth = 10.0;
final dashHeight = height;
final dashCount = (boxHeight / (2 * dashHeight)).floor();
return Flex(
children: List.generate(dashCount, (_) {
return SizedBox(
width: dashWidth,
height: dashHeight,
child: DecoratedBox(
decoration: BoxDecoration(color: color),
),
);
}),
mainAxisAlignment: MainAxisAlignment.spaceBetween,
direction: Axis.vertical,
);
},
),
);
}
}
您应该更喜欢使用 CustomPainter,因为它的性能更高,适合此类问题。
class DashLine extends StatelessWidget {
const DashLine({
Key key,
this.color,
this.dashWidth,
this.dashSpace,
this.strokeWidth,
}) : super(key: key);
final Color color;
final double dashWidth;
final double dashSpace;
final double strokeWidth;
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: _DashLinePainter(
color: color,
dashWidth: dashWidth,
dashSpace: dashSpace,
strokeWidth: strokeWidth,
),
);
}
}
class _DashLinePainter extends CustomPainter {
_DashLinePainter({
Color color,
double dashWidth,
double dashSpace,
double strokeWidth,
}) : _color = color ?? Colors.red,
_dashWidth = dashWidth ?? 5.0,
_dashSpace = dashSpace ?? 5.0,
_strokeWidth = strokeWidth ?? 1.0;
final Color _color;
final double _dashWidth;
final double _dashSpace;
final double _strokeWidth;
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = _color
..strokeWidth = _strokeWidth;
var max = size.width;
var startX = 0.0;
while (max >= 0) {
canvas.drawLine(Offset(startX, 0), Offset(startX + _dashWidth, 0), paint);
final space = (_dashSpace + _dashWidth);
startX += space;
max -= space;
}
}
@override
bool shouldRepaint(_DashLinePainter oldDelegate) {
return _color != oldDelegate._color ||
_dashWidth != oldDelegate._dashWidth ||
_dashSpace != oldDelegate._dashSpace ||
_strokeWidth != oldDelegate._strokeWidth;
}
}
您可以将 CustomPainter 与 线性渐变虚线着色器 一起用于线条。
// GradientRotation(3.14 / 2) — for vertical lines with dashes
// GradientRotation(0) — for horizontal lines with dashes
// .createShader(Rect.fromLTWH(0, 0, 10, 10) — 10 is the size of repeated shaders part
// This method can be tricky if you need a line oriented by some angle.
Paint()..shader = LinearGradient(
colors: [Colors.blue, Colors.transparent],
stops: [0.5, 0.5],
tileMode: TileMode.repeated,
transform: GradientRotation(3.14 / 2))
.createShader(Rect.fromLTWH(0, 0, 10, 10))
..style = PaintingStyle.stroke
..strokeWidth = 6
你可以使用这个:
Widget dashedHorizontalLine(){
return Row(
children: [
for (int i = 0; i < 20; i++)
Expanded(
child: Row(
children: [
Expanded(
child: Divider(
color: AppColors.darkGreen,
thickness: 2,
),
),
Expanded(
child: Container(),
),
],
),
),
],
);
}
我通过集成解决方案 here and the math from here 创建了一个 CustomPainter
。 CustomPainter
允许通过指定破折号的长度和破折号之间 space 的长度来绘制实线或虚线。但最好的是,您甚至可以在各个方向绘制实线或虚线。我的意思是水平的,垂直的,甚至是对角线的!
这是 CustomPainter
的代码:
import 'dart:math';
import 'package:flutter/material.dart';
class LinePainter extends CustomPainter {
final Offset firstOffset;
final Offset secondOffset;
final Color color;
final double strokeWidth;
final double dashLength;
final double dashSpace;
const LinePainter({
required this.firstOffset,
required this.secondOffset,
this.color = Colors.black,
this.strokeWidth = 2.0,
this.dashLength = 4.0,
this.dashSpace = 4.0,
});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..strokeWidth = strokeWidth;
_drawDashedLine(
dashLength, dashSpace, firstOffset, secondOffset, canvas, size, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
void _drawDashedLine(double dashLength, double dashSpace, Offset firstOffset,
Offset secondOffset, Canvas canvas, Size size, Paint paint) {
var startOffset = firstOffset;
var intervals = _getDirectionVector(firstOffset, secondOffset).length /
(dashLength + dashSpace);
for (var i = 0; i < intervals; i++) {
var endOffset = _getNextOffset(startOffset, secondOffset, dashLength);
/// Draw a small line.
canvas.drawLine(startOffset, endOffset, paint);
/// Update the starting offset.
startOffset = _getNextOffset(endOffset, secondOffset, dashSpace);
}
}
Offset _getNextOffset(
Offset firstOffset,
Offset secondOffset,
double smallVectorLength,
) {
var directionVector = _getDirectionVector(firstOffset, secondOffset);
var rescaleFactor = smallVectorLength / directionVector.length;
if (rescaleFactor.isNaN || rescaleFactor.isInfinite) {
rescaleFactor = 1;
}
var rescaledVector = Offset(directionVector.vector.dx * rescaleFactor,
directionVector.vector.dy * rescaleFactor);
var newOffset = Offset(
firstOffset.dx + rescaledVector.dx, firstOffset.dy + rescaledVector.dy);
return newOffset;
}
DirectionVector _getDirectionVector(Offset firstVector, Offset secondVector) {
var directionVector = Offset(
secondVector.dx - firstVector.dx, secondVector.dy - firstVector.dy);
var directionVectorLength =
sqrt(pow(directionVector.dx, 2) + pow(directionVector.dy, 2));
return DirectionVector(
vector: directionVector,
length: directionVectorLength,
);
}
}
class DirectionVector {
final Offset vector;
final double length;
const DirectionVector({
required this.vector,
required this.length,
});
}
您可以通过设置 CustomPaint
小部件的 painter
参数来使用此 CustomPainter
,如下所示:
CustomPaint(
painter: LinePainter(
firstOffset: Offset(0, 0),
secondOffset: Offset(10, 10),
),
),
结果如下图所示:
使用 dotted_line: ^3.0.0
提供虚线和更多功能的库 link
import 'package:dotted_line/dotted_line.dart';
DottedLine(
direction: Axis.horizontal,
lineLength: double.infinity,
lineThickness: 1.0,
dashLength: 4.0,
dashColor: Colors.grey,
dashRadius: 0.0,
dashGapLength: 4.0,
dashGapColor: Colors.transparent,
dashGapRadius: 0.0,
)
输出:
Container(
color: Colors.white,
height: 40.0,
child: Center(
child: Text(
"---------------------------------------------------------------------------",
maxLines: 1,
style: typoNormalTextRegular.copyWith(
color: colorABGray),
),
),
),
Only use Text Widget, easy solution
我想到了这个解决方案。
Row( // Dashed line
children: [
for (int i = 0; i < 25; i++)
Container(
width: 5,
height: 1,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 1,
color: i % 2 == 0
? const Color.fromRGBO(214, 211, 211, 1)
: Colors.transparent,
),
),
),
),
],
),
输出:
感谢 marksimr 的回答,这里是垂直和水平虚线的代码。
水平用法:
DashLineView(
fillRate: 0.7,
),
垂直 用法:
DashLineView(
fillRate: 0.7,
direction: Axis.vertical,
),
完整代码:
class DashLineView extends StatelessWidget {
final double dashHeight;
final double dashWith;
final Color dashColor;
final double fillRate; // [0, 1] totalDashSpace/totalSpace
final Axis direction;
DashLineView(
{this.dashHeight = 1,
this.dashWith = 8,
this.dashColor = Colors.black,
this.fillRate = 0.5,
this.direction = Axis.horizontal});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final boxSize = direction == Axis.horizontal
? constraints.constrainWidth()
: constraints.constrainHeight();
final dCount = (boxSize * fillRate / dashWith).floor();
return Flex(
children: List.generate(dCount, (_) {
return SizedBox(
width: direction == Axis.horizontal ? dashWith : dashHeight,
height: direction == Axis.horizontal ? dashHeight : dashWith,
child: DecoratedBox(
decoration: BoxDecoration(color: dashColor),
),
);
}),
mainAxisAlignment: MainAxisAlignment.spaceBetween,
direction: direction,
);
},
);
}
}
Row(
children: List.generate(20, (index) {
return Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Container(
height: 5,
width: 10,
color: Color(0XFFf2f2f2),
),
),
);
}),
)
以下代码不仅为线条创建虚线路径,而且为任何您想要虚线的路径创建虚线路径。
演示:
想法是采用 originalPath
并沿着它移动,交替添加破折号和间隙,直到提取出整个路径:
Path _getDashedPath(
Path originalPath,
double dashLength,
double dashGapLength,
) {
final metricsIterator = originalPath.computeMetrics().iterator;
while (metricsIterator.moveNext()) {
final metric = metricsIterator.current;
_dashedPathProperties.extractedPathLength = 0.0;
while (_dashedPathProperties.extractedPathLength < metric.length) {
if (_dashedPathProperties.addDashNext) {
_dashedPathProperties.addDash(metric, dashLength);
} else {
_dashedPathProperties.addDashGap(metric, dashGapLength);
}
}
}
return _dashedPathProperties.path;
}
我创建了一个 class DashedPathProperties
来跟踪当前 extractedPathLength
或 _remainingDashLength
之类的东西,如果 originalPath
包含多个sub-paths 和破折号(或破折号间隙)必须在下一个 sub-path:
上继续
class DashedPathProperties {
double extractedPathLength;
Path path;
final double _dashLength;
double _remainingDashLength;
double _remainingDashGapLength;
bool _previousWasDash;
DashedPathProperties({
required this.path,
required double dashLength,
required double dashGapLength,
}) : assert(dashLength > 0.0, 'dashLength must be > 0.0'),
assert(dashGapLength > 0.0, 'dashGapLength must be > 0.0'),
_dashLength = dashLength,
_remainingDashLength = dashLength,
_remainingDashGapLength = dashGapLength,
_previousWasDash = false,
extractedPathLength = 0.0;
//...
}
你可以这样使用(如果你想确保画家不能在边界之外绘画,你可以将你的 CustomPaint
包裹在 ClipRect
中):
CustomPaint(
painter: DashedPathPainter(
originalPath: Path()
..addOval(
const Rect.fromLTWH(0, 0, 100, 100),
),
pathColor: Colors.white,
),
size: const Size(100.0, 100.0),
)
您可以在 DartPad 中 运行 的完整示例代码:
import 'dart:ui' as ui;
import 'dart:math' as math;
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: const Scaffold(
body: Center(
child: ExampleDashedPath(),
),
),
);
}
}
class ExampleDashedPath extends StatelessWidget {
const ExampleDashedPath({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
const SizedBox(height: 50),
CustomPaint(
painter: DashedPathPainter(
originalPath: Path()..lineTo(100, 0),
pathColor: Colors.red,
strokeWidth: 5.0,
dashGapLength: 10.0,
dashLength: 10.0,
),
size: const Size(100.0, 2.0),
),
const SizedBox(height: 50),
CustomPaint(
painter: DashedPathPainter(
originalPath: Path()
..addOval(
const Rect.fromLTWH(0, 0, 100, 100),
),
pathColor: Colors.white,
),
size: const Size(100.0, 100.0),
),
const SizedBox(height: 50),
CustomPaint(
painter: DashedPathPainter(
originalPath: Path()
..addRect(
const Rect.fromLTWH(0, 0, 100, 100),
)
..lineTo(100, 100),
pathColor: Colors.grey,
strokeWidth: 2.0,
dashLength: 25.0,
),
size: const Size(100.0, 100.0),
),
],
);
}
}
class DashedPathPainter extends CustomPainter {
final Path originalPath;
final Color pathColor;
final double strokeWidth;
final double dashGapLength;
final double dashLength;
late DashedPathProperties _dashedPathProperties;
DashedPathPainter({
required this.originalPath,
required this.pathColor,
this.strokeWidth = 3.0,
this.dashGapLength = 5.0,
this.dashLength = 10.0,
});
@override
void paint(Canvas canvas, Size size) {
_dashedPathProperties = DashedPathProperties(
path: Path(),
dashLength: dashLength,
dashGapLength: dashGapLength,
);
final dashedPath = _getDashedPath(originalPath, dashLength, dashGapLength);
canvas.drawPath(
dashedPath,
Paint()
..style = PaintingStyle.stroke
..color = pathColor
..strokeWidth = strokeWidth,
);
}
@override
bool shouldRepaint(DashedPathPainter oldDelegate) =>
oldDelegate.originalPath != originalPath ||
oldDelegate.pathColor != pathColor ||
oldDelegate.strokeWidth != strokeWidth ||
oldDelegate.dashGapLength != dashGapLength ||
oldDelegate.dashLength != dashLength;
Path _getDashedPath(
Path originalPath,
double dashLength,
double dashGapLength,
) {
final metricsIterator = originalPath.computeMetrics().iterator;
while (metricsIterator.moveNext()) {
final metric = metricsIterator.current;
_dashedPathProperties.extractedPathLength = 0.0;
while (_dashedPathProperties.extractedPathLength < metric.length) {
if (_dashedPathProperties.addDashNext) {
_dashedPathProperties.addDash(metric, dashLength);
} else {
_dashedPathProperties.addDashGap(metric, dashGapLength);
}
}
}
return _dashedPathProperties.path;
}
}
class DashedPathProperties {
double extractedPathLength;
Path path;
final double _dashLength;
double _remainingDashLength;
double _remainingDashGapLength;
bool _previousWasDash;
DashedPathProperties({
required this.path,
required double dashLength,
required double dashGapLength,
}) : assert(dashLength > 0.0, 'dashLength must be > 0.0'),
assert(dashGapLength > 0.0, 'dashGapLength must be > 0.0'),
_dashLength = dashLength,
_remainingDashLength = dashLength,
_remainingDashGapLength = dashGapLength,
_previousWasDash = false,
extractedPathLength = 0.0;
bool get addDashNext {
if (!_previousWasDash || _remainingDashLength != _dashLength) {
return true;
}
return false;
}
void addDash(ui.PathMetric metric, double dashLength) {
// Calculate lengths (actual + available)
final end = _calculateLength(metric, _remainingDashLength);
final availableEnd = _calculateLength(metric, dashLength);
// Add path
final pathSegment = metric.extractPath(extractedPathLength, end);
path.addPath(pathSegment, Offset.zero);
// Update
final delta = _remainingDashLength - (end - extractedPathLength);
_remainingDashLength = _updateRemainingLength(
delta: delta,
end: end,
availableEnd: availableEnd,
initialLength: dashLength,
);
extractedPathLength = end;
_previousWasDash = true;
}
void addDashGap(ui.PathMetric metric, double dashGapLength) {
// Calculate lengths (actual + available)
final end = _calculateLength(metric, _remainingDashGapLength);
final availableEnd = _calculateLength(metric, dashGapLength);
// Move path's end point
ui.Tangent tangent = metric.getTangentForOffset(end)!;
path.moveTo(tangent.position.dx, tangent.position.dy);
// Update
final delta = end - extractedPathLength;
_remainingDashGapLength = _updateRemainingLength(
delta: delta,
end: end,
availableEnd: availableEnd,
initialLength: dashGapLength,
);
extractedPathLength = end;
_previousWasDash = false;
}
double _calculateLength(ui.PathMetric metric, double addedLength) {
return math.min(extractedPathLength + addedLength, metric.length);
}
double _updateRemainingLength({
required double delta,
required double end,
required double availableEnd,
required double initialLength,
}) {
return (delta > 0 && availableEnd == end) ? delta : initialLength;
}
}
class DividerWidget extends StatelessWidget {
const DividerWidget({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
height: 1,
color: Colors.black,
);
}
}
如何在 Flutter 中制作一个破折号?
As a workaround,对于你的情况,你可以这样做
class MySeparator extends StatelessWidget {
const MySeparator({Key? key, this.height = 1, this.color = Colors.black})
: super(key: key);
final double height;
final Color color;
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final boxWidth = constraints.constrainWidth();
const dashWidth = 10.0;
final dashHeight = height;
final dashCount = (boxWidth / (2 * dashWidth)).floor();
return Flex(
children: List.generate(dashCount, (_) {
return SizedBox(
width: dashWidth,
height: dashHeight,
child: DecoratedBox(
decoration: BoxDecoration(color: color),
),
);
}),
mainAxisAlignment: MainAxisAlignment.spaceBetween,
direction: Axis.horizontal,
);
},
);
}
}
并使用它const MySeparator()
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Material(
child: Container(
color: Colors.blue,
child: Center(
child: Container(
height: 600,
width: 350,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(16.0)),
),
child: Flex(
direction: Axis.vertical,
children: [
Expanded(child: Container()),
const MySeparator(color: Colors.grey),
Container(height: 200),
],
),
),
),
),
),
);
}
}
CustomPainter 在这里也可以提供帮助。在这个例子中是一条垂直虚线,但可以很容易地改变。
class LineDashedPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
var paint = Paint()..strokeWidth = 2;
var max = 35;
var dashWidth = 5;
var dashSpace = 5;
double startY = 0;
while (max >= 0) {
canvas.drawLine(Offset(0, startY), Offset(0, startY + dashWidth), paint);
final space = (dashSpace + dashWidth);
startY += space;
max -= space;
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
并且使用 CustomPaint Widget:
CustomPaint(painter: LineDashedPainter())
创建这个 class:
class DotWidget extends StatelessWidget {
final double totalWidth, dashWidth, emptyWidth, dashHeight;
final Color dashColor;
const DotWidget({
this.totalWidth = 300,
this.dashWidth = 10,
this.emptyWidth = 5,
this.dashHeight = 2,
this.dashColor = Colors.black,
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(
totalWidth ~/ (dashWidth + emptyWidth),
(_) => Container(
width: dashWidth,
height: dashHeight,
color: dashColor,
margin: EdgeInsets.only(left: emptyWidth / 2, right: emptyWidth / 2),
),
),
);
}
}
用法:
像使用任何其他小部件一样使用它
child: DotWidget(
dashColor: Colors.black,
dashHeight: 2,
dashWidth: 100,
)
这是水平虚线的代码,就像您的图片一样。 CustomPaint 是 flutter 团队强烈推荐的。渲染也快速高效。您可以使用 Offset 来改变方向。
class MyClass extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: CustomPaint(
painter: MyLinePainter(),
),
);
}
}
class MyLinePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
var max = 100;
var dashWidth, dashSpace = 5;
double startX = 0;
final paint = Paint()..color = Colors.grey;
while (max >= 0) {
canvas.drawLine(Offset(startX, 0), Offset(startX + dashWidth, 0), paint..strokeWidth = 1);
final space = (dashSpace + dashWidth);
startX += space;
max -= space;
}
}
我已经编写了 flutter_dash 库来绘制那个破折号。只有一行,你应该有一个破折号 :D
Dash(length: 200, dashColor: Colors.red)
试试吧!
试试这个,
class DotDivider extends StatelessWidget {
final double width;
final double height;
final double gap;
final Color color;
final double lineHeight;
const DotDivider(
{this.height = 1.0,
this.color = Colors.black,
this.width = 2.0,
this.gap = 2.0,
this.lineHeight = 10.0});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final boxWidth = constraints.constrainWidth();
final dashWidth = width;
final dashHeight = height;
final dashCount = (boxWidth / dashWidth).floor();
return Container(
height: (lineHeight * 2) + height,
child: ListView.builder(
physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
itemCount: dashCount,
itemBuilder: (BuildContext context, int index) => Center(
child: Container(
width: dashWidth,
height: dashHeight,
margin:
EdgeInsets.symmetric(vertical: lineHeight, horizontal: gap),
decoration: BoxDecoration(color: color),
),
),
),
);
},
);
}
}
class DashedLinePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
double dashWidth = 9, dashSpace = 5, startX = 0;
final paint = Paint()
..color = Colors.grey
..strokeWidth = 1;
while (startX < size.width) {
canvas.drawLine(Offset(startX, 0), Offset(startX + dashWidth, 0), paint);
startX += dashWidth + dashSpace;
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
// garis putus putus
Row(
children: List.generate(150~/10, (index) => Expanded(
child: Container(
color: index%2==0?Colors.transparent
:Colors.grey,
height: 2,
),
)),
),
垂直虚线:
我修改了 maksimr 的例子:
class DashedLine extends StatelessWidget {
final double height;
final double heightContainer;
final Color color;
const DashedLine({this.height = 3, this.color = Colors.black, this.heightContainer = 70});
@override
Widget build(BuildContext context) {
return Container(
height: heightContainer,
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final boxHeight = constraints.constrainHeight();
final dashWidth = 10.0;
final dashHeight = height;
final dashCount = (boxHeight / (2 * dashHeight)).floor();
return Flex(
children: List.generate(dashCount, (_) {
return SizedBox(
width: dashWidth,
height: dashHeight,
child: DecoratedBox(
decoration: BoxDecoration(color: color),
),
);
}),
mainAxisAlignment: MainAxisAlignment.spaceBetween,
direction: Axis.vertical,
);
},
),
);
}
}
您应该更喜欢使用 CustomPainter,因为它的性能更高,适合此类问题。
class DashLine extends StatelessWidget {
const DashLine({
Key key,
this.color,
this.dashWidth,
this.dashSpace,
this.strokeWidth,
}) : super(key: key);
final Color color;
final double dashWidth;
final double dashSpace;
final double strokeWidth;
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: _DashLinePainter(
color: color,
dashWidth: dashWidth,
dashSpace: dashSpace,
strokeWidth: strokeWidth,
),
);
}
}
class _DashLinePainter extends CustomPainter {
_DashLinePainter({
Color color,
double dashWidth,
double dashSpace,
double strokeWidth,
}) : _color = color ?? Colors.red,
_dashWidth = dashWidth ?? 5.0,
_dashSpace = dashSpace ?? 5.0,
_strokeWidth = strokeWidth ?? 1.0;
final Color _color;
final double _dashWidth;
final double _dashSpace;
final double _strokeWidth;
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = _color
..strokeWidth = _strokeWidth;
var max = size.width;
var startX = 0.0;
while (max >= 0) {
canvas.drawLine(Offset(startX, 0), Offset(startX + _dashWidth, 0), paint);
final space = (_dashSpace + _dashWidth);
startX += space;
max -= space;
}
}
@override
bool shouldRepaint(_DashLinePainter oldDelegate) {
return _color != oldDelegate._color ||
_dashWidth != oldDelegate._dashWidth ||
_dashSpace != oldDelegate._dashSpace ||
_strokeWidth != oldDelegate._strokeWidth;
}
}
您可以将 CustomPainter 与 线性渐变虚线着色器 一起用于线条。
// GradientRotation(3.14 / 2) — for vertical lines with dashes
// GradientRotation(0) — for horizontal lines with dashes
// .createShader(Rect.fromLTWH(0, 0, 10, 10) — 10 is the size of repeated shaders part
// This method can be tricky if you need a line oriented by some angle.
Paint()..shader = LinearGradient(
colors: [Colors.blue, Colors.transparent],
stops: [0.5, 0.5],
tileMode: TileMode.repeated,
transform: GradientRotation(3.14 / 2))
.createShader(Rect.fromLTWH(0, 0, 10, 10))
..style = PaintingStyle.stroke
..strokeWidth = 6
你可以使用这个:
Widget dashedHorizontalLine(){
return Row(
children: [
for (int i = 0; i < 20; i++)
Expanded(
child: Row(
children: [
Expanded(
child: Divider(
color: AppColors.darkGreen,
thickness: 2,
),
),
Expanded(
child: Container(),
),
],
),
),
],
);
}
我通过集成解决方案 here and the math from here 创建了一个 CustomPainter
。 CustomPainter
允许通过指定破折号的长度和破折号之间 space 的长度来绘制实线或虚线。但最好的是,您甚至可以在各个方向绘制实线或虚线。我的意思是水平的,垂直的,甚至是对角线的!
这是 CustomPainter
的代码:
import 'dart:math';
import 'package:flutter/material.dart';
class LinePainter extends CustomPainter {
final Offset firstOffset;
final Offset secondOffset;
final Color color;
final double strokeWidth;
final double dashLength;
final double dashSpace;
const LinePainter({
required this.firstOffset,
required this.secondOffset,
this.color = Colors.black,
this.strokeWidth = 2.0,
this.dashLength = 4.0,
this.dashSpace = 4.0,
});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..strokeWidth = strokeWidth;
_drawDashedLine(
dashLength, dashSpace, firstOffset, secondOffset, canvas, size, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
void _drawDashedLine(double dashLength, double dashSpace, Offset firstOffset,
Offset secondOffset, Canvas canvas, Size size, Paint paint) {
var startOffset = firstOffset;
var intervals = _getDirectionVector(firstOffset, secondOffset).length /
(dashLength + dashSpace);
for (var i = 0; i < intervals; i++) {
var endOffset = _getNextOffset(startOffset, secondOffset, dashLength);
/// Draw a small line.
canvas.drawLine(startOffset, endOffset, paint);
/// Update the starting offset.
startOffset = _getNextOffset(endOffset, secondOffset, dashSpace);
}
}
Offset _getNextOffset(
Offset firstOffset,
Offset secondOffset,
double smallVectorLength,
) {
var directionVector = _getDirectionVector(firstOffset, secondOffset);
var rescaleFactor = smallVectorLength / directionVector.length;
if (rescaleFactor.isNaN || rescaleFactor.isInfinite) {
rescaleFactor = 1;
}
var rescaledVector = Offset(directionVector.vector.dx * rescaleFactor,
directionVector.vector.dy * rescaleFactor);
var newOffset = Offset(
firstOffset.dx + rescaledVector.dx, firstOffset.dy + rescaledVector.dy);
return newOffset;
}
DirectionVector _getDirectionVector(Offset firstVector, Offset secondVector) {
var directionVector = Offset(
secondVector.dx - firstVector.dx, secondVector.dy - firstVector.dy);
var directionVectorLength =
sqrt(pow(directionVector.dx, 2) + pow(directionVector.dy, 2));
return DirectionVector(
vector: directionVector,
length: directionVectorLength,
);
}
}
class DirectionVector {
final Offset vector;
final double length;
const DirectionVector({
required this.vector,
required this.length,
});
}
您可以通过设置 CustomPaint
小部件的 painter
参数来使用此 CustomPainter
,如下所示:
CustomPaint(
painter: LinePainter(
firstOffset: Offset(0, 0),
secondOffset: Offset(10, 10),
),
),
结果如下图所示:
使用 dotted_line: ^3.0.0
提供虚线和更多功能的库 link
import 'package:dotted_line/dotted_line.dart';
DottedLine(
direction: Axis.horizontal,
lineLength: double.infinity,
lineThickness: 1.0,
dashLength: 4.0,
dashColor: Colors.grey,
dashRadius: 0.0,
dashGapLength: 4.0,
dashGapColor: Colors.transparent,
dashGapRadius: 0.0,
)
输出:
Container(
color: Colors.white,
height: 40.0,
child: Center(
child: Text(
"---------------------------------------------------------------------------",
maxLines: 1,
style: typoNormalTextRegular.copyWith(
color: colorABGray),
),
),
),
Only use Text Widget, easy solution
我想到了这个解决方案。
Row( // Dashed line
children: [
for (int i = 0; i < 25; i++)
Container(
width: 5,
height: 1,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 1,
color: i % 2 == 0
? const Color.fromRGBO(214, 211, 211, 1)
: Colors.transparent,
),
),
),
),
],
),
输出:
感谢 marksimr 的回答,这里是垂直和水平虚线的代码。
水平用法:
DashLineView(
fillRate: 0.7,
),
垂直 用法:
DashLineView(
fillRate: 0.7,
direction: Axis.vertical,
),
完整代码:
class DashLineView extends StatelessWidget {
final double dashHeight;
final double dashWith;
final Color dashColor;
final double fillRate; // [0, 1] totalDashSpace/totalSpace
final Axis direction;
DashLineView(
{this.dashHeight = 1,
this.dashWith = 8,
this.dashColor = Colors.black,
this.fillRate = 0.5,
this.direction = Axis.horizontal});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final boxSize = direction == Axis.horizontal
? constraints.constrainWidth()
: constraints.constrainHeight();
final dCount = (boxSize * fillRate / dashWith).floor();
return Flex(
children: List.generate(dCount, (_) {
return SizedBox(
width: direction == Axis.horizontal ? dashWith : dashHeight,
height: direction == Axis.horizontal ? dashHeight : dashWith,
child: DecoratedBox(
decoration: BoxDecoration(color: dashColor),
),
);
}),
mainAxisAlignment: MainAxisAlignment.spaceBetween,
direction: direction,
);
},
);
}
}
Row(
children: List.generate(20, (index) {
return Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Container(
height: 5,
width: 10,
color: Color(0XFFf2f2f2),
),
),
);
}),
)
以下代码不仅为线条创建虚线路径,而且为任何您想要虚线的路径创建虚线路径。
演示:
想法是采用 originalPath
并沿着它移动,交替添加破折号和间隙,直到提取出整个路径:
Path _getDashedPath(
Path originalPath,
double dashLength,
double dashGapLength,
) {
final metricsIterator = originalPath.computeMetrics().iterator;
while (metricsIterator.moveNext()) {
final metric = metricsIterator.current;
_dashedPathProperties.extractedPathLength = 0.0;
while (_dashedPathProperties.extractedPathLength < metric.length) {
if (_dashedPathProperties.addDashNext) {
_dashedPathProperties.addDash(metric, dashLength);
} else {
_dashedPathProperties.addDashGap(metric, dashGapLength);
}
}
}
return _dashedPathProperties.path;
}
我创建了一个 class DashedPathProperties
来跟踪当前 extractedPathLength
或 _remainingDashLength
之类的东西,如果 originalPath
包含多个sub-paths 和破折号(或破折号间隙)必须在下一个 sub-path:
class DashedPathProperties {
double extractedPathLength;
Path path;
final double _dashLength;
double _remainingDashLength;
double _remainingDashGapLength;
bool _previousWasDash;
DashedPathProperties({
required this.path,
required double dashLength,
required double dashGapLength,
}) : assert(dashLength > 0.0, 'dashLength must be > 0.0'),
assert(dashGapLength > 0.0, 'dashGapLength must be > 0.0'),
_dashLength = dashLength,
_remainingDashLength = dashLength,
_remainingDashGapLength = dashGapLength,
_previousWasDash = false,
extractedPathLength = 0.0;
//...
}
你可以这样使用(如果你想确保画家不能在边界之外绘画,你可以将你的 CustomPaint
包裹在 ClipRect
中):
CustomPaint(
painter: DashedPathPainter(
originalPath: Path()
..addOval(
const Rect.fromLTWH(0, 0, 100, 100),
),
pathColor: Colors.white,
),
size: const Size(100.0, 100.0),
)
您可以在 DartPad 中 运行 的完整示例代码:
import 'dart:ui' as ui;
import 'dart:math' as math;
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: const Scaffold(
body: Center(
child: ExampleDashedPath(),
),
),
);
}
}
class ExampleDashedPath extends StatelessWidget {
const ExampleDashedPath({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
const SizedBox(height: 50),
CustomPaint(
painter: DashedPathPainter(
originalPath: Path()..lineTo(100, 0),
pathColor: Colors.red,
strokeWidth: 5.0,
dashGapLength: 10.0,
dashLength: 10.0,
),
size: const Size(100.0, 2.0),
),
const SizedBox(height: 50),
CustomPaint(
painter: DashedPathPainter(
originalPath: Path()
..addOval(
const Rect.fromLTWH(0, 0, 100, 100),
),
pathColor: Colors.white,
),
size: const Size(100.0, 100.0),
),
const SizedBox(height: 50),
CustomPaint(
painter: DashedPathPainter(
originalPath: Path()
..addRect(
const Rect.fromLTWH(0, 0, 100, 100),
)
..lineTo(100, 100),
pathColor: Colors.grey,
strokeWidth: 2.0,
dashLength: 25.0,
),
size: const Size(100.0, 100.0),
),
],
);
}
}
class DashedPathPainter extends CustomPainter {
final Path originalPath;
final Color pathColor;
final double strokeWidth;
final double dashGapLength;
final double dashLength;
late DashedPathProperties _dashedPathProperties;
DashedPathPainter({
required this.originalPath,
required this.pathColor,
this.strokeWidth = 3.0,
this.dashGapLength = 5.0,
this.dashLength = 10.0,
});
@override
void paint(Canvas canvas, Size size) {
_dashedPathProperties = DashedPathProperties(
path: Path(),
dashLength: dashLength,
dashGapLength: dashGapLength,
);
final dashedPath = _getDashedPath(originalPath, dashLength, dashGapLength);
canvas.drawPath(
dashedPath,
Paint()
..style = PaintingStyle.stroke
..color = pathColor
..strokeWidth = strokeWidth,
);
}
@override
bool shouldRepaint(DashedPathPainter oldDelegate) =>
oldDelegate.originalPath != originalPath ||
oldDelegate.pathColor != pathColor ||
oldDelegate.strokeWidth != strokeWidth ||
oldDelegate.dashGapLength != dashGapLength ||
oldDelegate.dashLength != dashLength;
Path _getDashedPath(
Path originalPath,
double dashLength,
double dashGapLength,
) {
final metricsIterator = originalPath.computeMetrics().iterator;
while (metricsIterator.moveNext()) {
final metric = metricsIterator.current;
_dashedPathProperties.extractedPathLength = 0.0;
while (_dashedPathProperties.extractedPathLength < metric.length) {
if (_dashedPathProperties.addDashNext) {
_dashedPathProperties.addDash(metric, dashLength);
} else {
_dashedPathProperties.addDashGap(metric, dashGapLength);
}
}
}
return _dashedPathProperties.path;
}
}
class DashedPathProperties {
double extractedPathLength;
Path path;
final double _dashLength;
double _remainingDashLength;
double _remainingDashGapLength;
bool _previousWasDash;
DashedPathProperties({
required this.path,
required double dashLength,
required double dashGapLength,
}) : assert(dashLength > 0.0, 'dashLength must be > 0.0'),
assert(dashGapLength > 0.0, 'dashGapLength must be > 0.0'),
_dashLength = dashLength,
_remainingDashLength = dashLength,
_remainingDashGapLength = dashGapLength,
_previousWasDash = false,
extractedPathLength = 0.0;
bool get addDashNext {
if (!_previousWasDash || _remainingDashLength != _dashLength) {
return true;
}
return false;
}
void addDash(ui.PathMetric metric, double dashLength) {
// Calculate lengths (actual + available)
final end = _calculateLength(metric, _remainingDashLength);
final availableEnd = _calculateLength(metric, dashLength);
// Add path
final pathSegment = metric.extractPath(extractedPathLength, end);
path.addPath(pathSegment, Offset.zero);
// Update
final delta = _remainingDashLength - (end - extractedPathLength);
_remainingDashLength = _updateRemainingLength(
delta: delta,
end: end,
availableEnd: availableEnd,
initialLength: dashLength,
);
extractedPathLength = end;
_previousWasDash = true;
}
void addDashGap(ui.PathMetric metric, double dashGapLength) {
// Calculate lengths (actual + available)
final end = _calculateLength(metric, _remainingDashGapLength);
final availableEnd = _calculateLength(metric, dashGapLength);
// Move path's end point
ui.Tangent tangent = metric.getTangentForOffset(end)!;
path.moveTo(tangent.position.dx, tangent.position.dy);
// Update
final delta = end - extractedPathLength;
_remainingDashGapLength = _updateRemainingLength(
delta: delta,
end: end,
availableEnd: availableEnd,
initialLength: dashGapLength,
);
extractedPathLength = end;
_previousWasDash = false;
}
double _calculateLength(ui.PathMetric metric, double addedLength) {
return math.min(extractedPathLength + addedLength, metric.length);
}
double _updateRemainingLength({
required double delta,
required double end,
required double availableEnd,
required double initialLength,
}) {
return (delta > 0 && availableEnd == end) ? delta : initialLength;
}
}
class DividerWidget extends StatelessWidget {
const DividerWidget({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
height: 1,
color: Colors.black,
);
}
}