前言:简书菜鸟一枚,如有侵权,望告知,我会下架文章;
时间轴,推荐文章:https://blog.csdn.net/m0_37667770/article/details/93589084
这篇文章写得比较齐全,但是与我实际遇到的情况还是要略加变动的。
实际中遇到想要的效果图:因为涉及公司权益,故仅保留时间轴效果。
先上效果图:
实现方法:
1.垂直列表,首选ListView。
即时间轴中最外层应该是个ListView,每个具体的日期是一个item。初步实现:(大的点是与日期同级的,故这里用row水平布局类似Android中的LinearLayout的horizon;)
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'DashBorder.dart';
import 'JourneyBean.dart';
/**
* author walke
* date: 2020/4/28
* des:我的旅程页面
* 时间轴效果 参考 :https://blog.csdn.net/m0_37667770/article/details/93589084
*/
class MyJourneyPageTest extends StatefulWidget {
static const routeName = 'MyJourneyPageTest';
Map<String, dynamic> jsonData = {
"result": 0,
"description": "操作成功",
"list": [
{
"timeLine": "2020-05-20",
"journeyList": [
{
"place": "华山论剑",
"templateText": "Flutter —— 时间轴 实现,自定义点边框PointBorder 这一天,我完成了",
},
{"place": "华山煮茶", "templateText": "这一天,我完成了"}
]
},
{
"timeLine": "2020-05-19",
"journeyList": [
{
"place": "泰山悟道",
"templateText": "Flutter —— 时间轴 实现,自定义点边框PointBorder 这一天,我完成了",
},
{
"place": "泰山喝茶",
"templateText": "这一天,我完成了",
}
]
},
{
"timeLine": "2020-05-10",
"journeyList": [
{
"place": "嵩山比武",
"templateText": "Flutter —— 时间轴 实现,自定义点边框PointBorder 这一天,我完成了",
},
{
"place": "嵩山习武",
"templateText": "Flutter —— 时间轴 实现,自定义点边框PointBorder 这一天,我完成了",
}
]
},
{
"timeLine": "2020-05-09",
"journeyList": [
{
"place": "衡山打酱油",
"templateText": "Flutter —— 时间轴 实现,自定义点边框PointBorder 这一天,我完成了",
}
]
}
],
};
@override
State<StatefulWidget> createState() => _MyJourneyPageState();
}
class _MyJourneyPageState extends State<MyJourneyPageTest> {
JourneyBean _journeyResultBean;
List<JourneyTimeLine> _listJourneyTimeLine = [];
bool isEmpty = false;
bool isLoading = false;
ScrollController _scrollController = new ScrollController();
@override
void initState() {
super.initState();
_journeyResultBean = JourneyBean.fromJson(widget.jsonData);
_scrollController.addListener(() {});
}
@override
Widget build(BuildContext context) {
_listJourneyTimeLine.addAll(_journeyResultBean.list);
_listJourneyTimeLine.add(_journeyResultBean.list[0]);
return Stack(
children: <Widget>[
Container(
color: Colors.green,
height: 150,
),
Scaffold(
backgroundColor: Colors.transparent,
appBar: PreferredSize(
child: AppBar(
elevation: 0,
centerTitle: true,
title: Text(
'我的旅程',
),
backgroundColor: Colors.green,
),
preferredSize: Size.fromHeight(40.0)),
body: Card(
margin: EdgeInsets.only(top: 30, left: 15.0, right: 15.0, bottom: 15.0),
color: Colors.white,
child: _getMainContainer(),
),
),
],
);
}
_getMainContainer() {
return MediaQuery.removePadding(
removeTop: true,
context: context,
child: ListView.builder(
controller: _scrollController,
itemCount: _listJourneyTimeLine.length ,
itemBuilder: (context, index) {
return _detailView(_listJourneyTimeLine[index ]);
},
));
}
// 旅程详情
_detailView(JourneyTimeLine timeLine) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
// 日期行 , 点+日期
Row(
children: <Widget>[
Container( // 点
child: CircleAvatar(
backgroundColor: Colors.greenAccent,
radius: 6,
),
alignment: Alignment.center,
width: 30,
margin: EdgeInsets.only(left: 8, right: 3),
),
Text( // 日期
dateChane(timeLine.timeLine),
style: TextStyle(fontWeight: FontWeight.bold),
textAlign: TextAlign.left,
),
],
),
// 单个日期具体行程
_detailView2(timeLine),
],
);
}
// 单个日期具体行程
_detailView2(JourneyTimeLine timeLine) {
return Row(
children: <Widget>[
Container(
// 点
child: CircleAvatar(
backgroundColor: Colors.greenAccent,
radius: 2,
),
alignment: Alignment.center,
width: 30,
margin: EdgeInsets.only(left: 8, right: 3),
),
Container(
alignment: Alignment.centerLeft,
margin: EdgeInsets.only(
left: 0,
top: 1,
),
padding: EdgeInsets.only(top: 0, bottom: 8),
color: Colors.blue[100],
child: Text("稍后实现具体日期的游玩列表"),
)
],
);
}
}
/// 时间日期转化
String dateChane(String timeLine) {
print('walke MyJourneyPageState._dateChane() -----> $timeLine');
List<String> dlist = timeLine.split('-');
if (dlist == null || dlist.length == 0) return timeLine;
int month = int.parse(dlist[1]); //
int day = int.parse(dlist[2]);
return dlist[0] + '年' + month.toString() + '月' + day.toString() + '日';
}
效果如图:;
2.然后适当改变实现ListView的代码,用于添加顶部的点。
_getMainContainer() {
return MediaQuery.removePadding(
removeTop: true,
context: context,
child: ListView.builder(
controller: _scrollController,
itemCount: _listJourneyTimeLine.length + 1,
itemBuilder: (context, index) {
if (index == 0) {
return Column( // 顶部的点
mainAxisAlignment: MainAxisAlignment.start,
children: [1, 2, 3].map((ite) {
return Container( // 点
child: CircleAvatar(
backgroundColor: Colors.greenAccent,
radius: 2,
),
alignment: Alignment.topLeft,
margin: EdgeInsets.only(left: 21, right: 10, top: (ite == 1 ? 3 : 8)), // 区别间距
// color: Color(0x36FFA726),
);
}).toList(),
);
} else {
return _detailView(_listJourneyTimeLine[index - 1]);
}
},
));
}
3.观察可知具体日期里的行程个数是不定的,每个行程的内容(字数也是不定的)。故具体日期中,行程列表与左边的小点不是一对一的关系,不该用一起在一个listviewi的tem中,即具体行程不用listview。尝试:使用Row左右分开,小点与行程左右分开(参考顶部的点的方式,暂时假定点的个数是行程数*3):主要代码:
// 单个日期具体行程
_detailView2(JourneyTimeLine timeLine) {
List<Journey> points = [];
points.addAll(timeLine.journeyList);
points.addAll(timeLine.journeyList);
points.addAll(timeLine.journeyList);
return Row(
children: <Widget>[
// 左边的点
Column(
mainAxisAlignment: MainAxisAlignment.start,
children: points.map((ite) {
return Container(
// 点
child: CircleAvatar(
backgroundColor: Colors.green,
radius: 1,
),
alignment: Alignment.topLeft,
margin: EdgeInsets.only(left: 22, right: 10, top: 8),
// color: Color(0x36FFA726),
);
}).toList(),
),
// 右边具体的行程
Expanded(
child: Container( // 有超出左边故外层加了Expanded
alignment: Alignment.centerLeft,
margin: EdgeInsets.only(
left: 0,
top: 1,
right: 10,
),
padding: EdgeInsets.only(top: 0, bottom: 8),
color: Colors.blue[100],
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
children: timeLine.journeyList.map((Journey item) {
return Container(
alignment: Alignment.topLeft,
margin: EdgeInsets.only(right: 12, top: 9),
child: RichText(
// 富文本显示
text: TextSpan(children: [
TextSpan(
text: item.templateText,
style: TextStyle(
color: Colors.black87,
fontSize: 13,
),
),
TextSpan(
text: " " + item.place,
style: TextStyle(color: Colors.green, fontSize: 13),
recognizer: TapGestureRecognizer()
..onTap = () {
// 点击事件
// ToastUtils.showTs(item.keyText);
}),
]),
));
}).toList(),
),
)),
],
);
}
效果如图,有点像想要的效果了:
4.这时我们就要思考,具体行程的内容与左边小的点的对应关系了。
比较UI给的效果图(图1)。左边小点应该与行程所占高度范围应该是相对一样的。这时,可以考虑两个方法。①获取完成绘制时,行程内容所占高度,即对应的Colum的高度cHeight。然后通过cHeight/小点间隔求出小点个数。从而实现左边小点。②.小点高度与行程高度一样,结合从文章https://blog.csdn.net/m0_37667770/article/details/93589084给的灵感。我想起Android在边框的实现边框时可以通过xml的shap简易实现虚线边框、圆点边框、单边框。那我们能不能通过实现一个左边框为圆点边框的方式去实现呢?这样能有效解耦代码,减少Widget树的层级和代码。由于我是Android搬砖经验丰富所以很倾向于方法②,弄好了也没去尝试方法①,所以①就有由各位有兴趣的去烟酒一下了。以下是实现单边框的尝试及步骤。
5.实现单边框。
1). 先试试边框合适不合适, 调整_detailView2代码:(发现flutter已经有API实现左边宽Border(left: BorderSide(color: Color(0x66FF9800), width: 3)),但无点边框)
// 单个日期具体行程
_detailView2(JourneyTimeLine timeLine) {
List<Journey> points = [];
points.addAll(timeLine.journeyList);
points.addAll(timeLine.journeyList);
points.addAll(timeLine.journeyList);
return Stack(
children: <Widget>[
// 左边的点
Column(
mainAxisAlignment: MainAxisAlignment.start,
children: points.map((ite) {
return Container(
// 点
child: CircleAvatar(
backgroundColor: Colors.green,
radius: 1,
),
alignment: Alignment.topLeft,
margin: EdgeInsets.only(left: 22, right: 10, top: 8),
// color: Color(0x36FFA726),
);
}).toList(),
),
// 右边具体的行程
Container( // 有超出左边故外层加了Expanded【外层改为Stack,就要去掉Expanded】
alignment: Alignment.centerLeft,
margin: EdgeInsets.only(
left: 22,
top: 1,
right: 10,
),
padding: EdgeInsets.only(top: 0, bottom: 8, left: 13),
// color: Colors.blue[100],// 使用了decoration就不能使用这个属性了,报错:Cannot provide both a color and a decoratio
decoration: BoxDecoration(
border: Border(left: BorderSide(color: Color(0x66FF9800), width: 3)), // 左边边框
),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
children: timeLine.journeyList.map((Journey item) {
return Container(
alignment: Alignment.topLeft,
margin: EdgeInsets.only(right: 12, top: 9),
child: RichText( // 富文本显示
text: TextSpan(children: [
TextSpan(
text: item.templateText,
style: TextStyle(
color: Colors.black87,
fontSize: 13,
),
),
TextSpan(
text: " " + item.place,
style: TextStyle(color: Colors.green, fontSize: 13),
recognizer: TapGestureRecognizer()
..onTap = () {
// 点击事件
// ToastUtils.showTs(item.keyText);
}),
]),
));
}).toList(),
),
)
],
);
}
效果如图:有戏!
2). 查看Border源码,去看看左边宽是如何实现的。
Border部分源码:【】对应文件:box_border.dart】
class Border extends BoxBorder {
// ... 省略大部分代码
const Border({
this.top = BorderSide.none,
this.right = BorderSide.none,
this.bottom = BorderSide.none,
this.left = BorderSide.none,
}) : assert(top != null),
assert(right != null),
assert(bottom != null),
assert(left != null);
/// * [paintBorder], which is used if the border is not uniform.
@override
void paint(
Canvas canvas,
Rect rect, {
TextDirection textDirection,
BoxShape shape = BoxShape.rectangle,
BorderRadius borderRadius,
}) {
if (isUniform) {
switch (top.style) {
case BorderStyle.none:
return;
case BorderStyle.solid:
switch (shape) {
case BoxShape.circle:
assert(borderRadius == null, 'A borderRadius can only be given for rectangular boxes.');
BoxBorder._paintUniformBorderWithCircle(canvas, rect, top);
break;
case BoxShape.rectangle:
if (borderRadius != null) {
BoxBorder._paintUniformBorderWithRadius(canvas, rect, top, borderRadius);
return;
}
BoxBorder._paintUniformBorderWithRectangle(canvas, rect, top);
break;
}
return;
}
}
assert(borderRadius == null, 'A borderRadius can only be given for uniform borders.');
assert(shape == BoxShape.rectangle, 'A border can only be drawn as a circle if it is uniform.');
paintBorder(canvas, rect, top: top, right: right, bottom: bottom, left: left);
}
}
点进Border源码首页查看构造方法,发现有个left,即我们关心的。然后全局搜索,中的在于与paint、draw这些绘制有关的。或者看继续传给了哪个类,继续去看,这里我们较容易就可以看到paint()方法,然后有paintBorder方法调用。进而我们查看paintBorder的代码:
void paintBorder(
Canvas canvas,
Rect rect, {
BorderSide top = BorderSide.none,
BorderSide right = BorderSide.none,
BorderSide bottom = BorderSide.none,
BorderSide left = BorderSide.none,
}) {
assert(canvas != null);
assert(rect != null);
assert(top != null);
assert(right != null);
assert(bottom != null);
assert(left != null);
// We draw the borders as filled shapes, unless the borders are hairline
// borders, in which case we use PaintingStyle.stroke, with the stroke width
// specified here.
final Paint paint = Paint()
..strokeWidth = 0.0;
final Path path = Path();
switch (top.style) {
case BorderStyle.solid:
paint.color = top.color;
path.reset();
path.moveTo(rect.left, rect.top);
path.lineTo(rect.right, rect.top);
if (top.width == 0.0) {
paint.style = PaintingStyle.stroke;
} else {
paint.style = PaintingStyle.fill;
path.lineTo(rect.right - right.width, rect.top + top.width);
path.lineTo(rect.left + left.width, rect.top + top.width);
}
canvas.drawPath(path, paint);
break;
case BorderStyle.none:
break;
}
switch (right.style) {
case BorderStyle.solid:
paint.color = right.color;
path.reset();
path.moveTo(rect.right, rect.top);
path.lineTo(rect.right, rect.bottom);
if (right.width == 0.0) {
paint.style = PaintingStyle.stroke;
} else {
paint.style = PaintingStyle.fill;
path.lineTo(rect.right - right.width, rect.bottom - bottom.width);
path.lineTo(rect.right - right.width, rect.top + top.width);
}
canvas.drawPath(path, paint);
break;
case BorderStyle.none:
break;
}
switch (bottom.style) {
case BorderStyle.solid:
paint.color = bottom.color;
path.reset();
path.moveTo(rect.right, rect.bottom);
path.lineTo(rect.left, rect.bottom);
if (bottom.width == 0.0) {
paint.style = PaintingStyle.stroke;
} else {
paint.style = PaintingStyle.fill;
path.lineTo(rect.left + left.width, rect.bottom - bottom.width);
path.lineTo(rect.right - right.width, rect.bottom - bottom.width);
}
canvas.drawPath(path, paint);
break;
case BorderStyle.none:
break;
}
switch (left.style) {
case BorderStyle.solid:
paint.color = left.color;
path.reset();
path.moveTo(rect.left, rect.bottom);
path.lineTo(rect.left, rect.top);
if (left.width == 0.0) {
paint.style = PaintingStyle.stroke;
} else {
paint.style = PaintingStyle.fill;
path.lineTo(rect.left + left.width, rect.top + top.width);
path.lineTo(rect.left + left.width, rect.bottom - bottom.width);
}
canvas.drawPath(path, paint);
break;
case BorderStyle.none:
break;
}
}
搜索传入的left,即可快速定位: switch (left.style)这就是我们要找的。发现有paint(画笔)、path(轨迹)、canvas(画布)【自定义Widget方面的知识点】,即这里就是具体实现左边宽的代码。
3). 实现点边框,其实就是想办法改造2)发现的switch (left.style)中的代码。
简单粗暴的方式--Copy源码新建自定义类。先动手试试。新建一个PointBorder。复制Border的代码到PointBorder,然后改导包、改关联。处理完报错。注:如果发现处理不完报错,可能是不适合这种方式,这个合适。
可得:也顺道改成想要的效果
import 'dart:ui';
import 'package:flutter/material.dart';
/**
* author walke
* date: 2020/4/30
* des: 左边框变成点边框,用于弄时间轴
*/
///
class PointBorder extends BoxBorder {
const PointBorder({
this.top = BorderSide.none,
this.right = BorderSide.none,
this.bottom = BorderSide.none,
this.left = BorderSide.none,
})
: assert(top != null),
assert(right != null),
assert(bottom != null),
assert(left != null);
const PointBorder.fromBorderSide(BorderSide side)
: assert(side != null),
top = side,
right = side,
bottom = side,
left = side;
factory PointBorder.all({
Color color = const Color(0xFF000000),
double width = 1.0,
BorderStyle style = BorderStyle.solid,
}) {
final BorderSide side = BorderSide(color: color, width: width, style: style);
return PointBorder.fromBorderSide(side);
}
static PointBorder merge(PointBorder a, PointBorder b) {
assert(a != null);
assert(b != null);
assert(BorderSide.canMerge(a.top, b.top));
assert(BorderSide.canMerge(a.right, b.right));
assert(BorderSide.canMerge(a.bottom, b.bottom));
assert(BorderSide.canMerge(a.left, b.left));
return PointBorder(
top: BorderSide.merge(a.top, b.top),
right: BorderSide.merge(a.right, b.right),
bottom: BorderSide.merge(a.bottom, b.bottom),
left: BorderSide.merge(a.left, b.left),
);
}
@override
final BorderSide top;
/// The right side of this border.
final BorderSide right;
@override
final BorderSide bottom;
/// The left side of this border.
final BorderSide left;
@override
EdgeInsetsGeometry get dimensions {
return EdgeInsets.fromLTRB(left.width, top.width, right.width, bottom.width);
}
@override
bool get isUniform {
final Color topColor = top.color;
if (right.color != topColor ||
bottom.color != topColor ||
left.color != topColor)
return false;
final double topWidth = top.width;
if (right.width != topWidth ||
bottom.width != topWidth ||
left.width != topWidth)
return false;
final BorderStyle topStyle = top.style;
if (right.style != topStyle ||
bottom.style != topStyle ||
left.style != topStyle)
return false;
return true;
}
@override
PointBorder add(ShapeBorder other, { bool reversed = false }) {
if (other is! PointBorder)
return null;
final PointBorder typedOther = other;
if (BorderSide.canMerge(top, typedOther.top) &&
BorderSide.canMerge(right, typedOther.right) &&
BorderSide.canMerge(bottom, typedOther.bottom) &&
BorderSide.canMerge(left, typedOther.left)) {
return PointBorder.merge(this, typedOther);
}
return null;
}
@override
PointBorder scale(double t) {
return PointBorder(
top: top.scale(t),
right: right.scale(t),
bottom: bottom.scale(t),
left: left.scale(t),
);
}
@override
ShapeBorder lerpFrom(ShapeBorder a, double t) {
if (a is PointBorder)
return PointBorder.lerp(a, this, t);
return super.lerpFrom(a, t);
}
@override
ShapeBorder lerpTo(ShapeBorder b, double t) {
if (b is PointBorder)
return PointBorder.lerp(this, b, t);
return super.lerpTo(b, t);
}
/// {@macro dart.ui.shadow.lerp}
static PointBorder lerp(PointBorder a, PointBorder 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);
return PointBorder(
top: BorderSide.lerp(a.top, b.top, t),
right: BorderSide.lerp(a.right, b.right, t),
bottom: BorderSide.lerp(a.bottom, b.bottom, t),
left: BorderSide.lerp(a.left, b.left, t),
);
}
@override
void paint(Canvas canvas,
Rect rect, {
TextDirection textDirection,
BoxShape shape = BoxShape.rectangle,
BorderRadius borderRadius,
}) {
if (isUniform) {
switch (top.style) {
case BorderStyle.none:
return;
case BorderStyle.solid:
switch (shape) {
case BoxShape.circle:
assert(borderRadius == null, 'A borderRadius can only be given for rectangular boxes.');
_paintUniformBorderWithCircle(canvas, rect, top);
break;
case BoxShape.rectangle:
if (borderRadius != null) {
_paintUniformBorderWithRadius(canvas, rect, top, borderRadius);
return;
}
_paintUniformBorderWithRectangle(canvas, rect, top);
break;
}
return;
}
}
assert(borderRadius == null, 'A borderRadius can only be given for uniform borders.');
assert(shape == BoxShape.rectangle, 'A border can only be drawn as a circle if it is uniform.');
paintBorder2(canvas, rect, top: top, right: right, bottom: bottom, left: left);
}
static void _paintUniformBorderWithCircle(Canvas canvas, Rect rect, BorderSide side) {
assert(side.style != BorderStyle.none);
final double width = side.width;
final Paint paint = side.toPaint();
final double radius = (rect.shortestSide - width) / 2.0;
canvas.drawCircle(rect.center, radius, paint);
}
static void _paintUniformBorderWithRadius(Canvas canvas, Rect rect, BorderSide side,
BorderRadius borderRadius) {
assert(side.style != BorderStyle.none);
final Paint paint = Paint()
..color = side.color;
final RRect outer = borderRadius.toRRect(rect);
final double width = side.width;
if (width == 0.0) {
paint
..style = PaintingStyle.stroke
..strokeWidth = 0.0;
canvas.drawRRect(outer, paint);
} else {
final RRect inner = outer.deflate(width);
canvas.drawDRRect(outer, inner, paint);
}
}
static void _paintUniformBorderWithRectangle(Canvas canvas, Rect rect, BorderSide side) {
assert(side.style != BorderStyle.none);
final double width = side.width;
final Paint paint = side.toPaint();
// canvas.drawRect(rect.deflate(width / 2.0), paint);
List<Offset> pionts = [];
double h = rect.height;
int interval = 10; // 间距
int length = (h / interval) as int;
for (int i = 1; i < length-1; i++) {
pionts.add(Offset(0, h / interval*i));
}
canvas.drawPoints(PointMode.points, pionts, paint);
}
@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (runtimeType != other.runtimeType)
return false;
final PointBorder typedOther = other;
return top == typedOther.top &&
right == typedOther.right &&
bottom == typedOther.bottom &&
left == typedOther.left;
}
@override
int get hashCode => hashValues(top, right, bottom, left);
@override
String toString() {
if (isUniform)
return '$runtimeType.all($top)';
final List<String> arguments = <String>[];
if (top != BorderSide.none)
arguments.add('top: $top');
if (right != BorderSide.none)
arguments.add('right: $right');
if (bottom != BorderSide.none)
arguments.add('bottom: $bottom');
if (left != BorderSide.none)
arguments.add('left: $left');
return '$runtimeType(${arguments.join(", ")})';
}
}
void paintBorder2(
Canvas canvas,
Rect rect, {
BorderSide top = BorderSide.none,
BorderSide right = BorderSide.none,
BorderSide bottom = BorderSide.none,
BorderSide left = BorderSide.none,
}) {
assert(canvas != null);
assert(rect != null);
assert(top != null);
assert(right != null);
assert(bottom != null);
assert(left != null);
final Paint paint = Paint()
..strokeWidth = 0.0;
final Path path = Path();
switch (top.style) {
case BorderStyle.solid:
paint.color = top.color;
path.reset();
path.moveTo(rect.left, rect.top);
path.lineTo(rect.right, rect.top);
if (top.width == 0.0) {
paint.style = PaintingStyle.stroke;
} else {
paint.style = PaintingStyle.fill;
path.lineTo(rect.right - right.width, rect.top + top.width);
path.lineTo(rect.left + left.width, rect.top + top.width);
}
canvas.drawPath(path, paint);
break;
case BorderStyle.none:
break;
}
switch (right.style) {
case BorderStyle.solid:
paint.color = right.color;
path.reset();
path.moveTo(rect.right, rect.top);
path.lineTo(rect.right, rect.bottom);
if (right.width == 0.0) {
paint.style = PaintingStyle.stroke;
} else {
paint.style = PaintingStyle.fill;
path.lineTo(rect.right - right.width, rect.bottom - bottom.width);
path.lineTo(rect.right - right.width, rect.top + top.width);
}
canvas.drawPath(path, paint);
break;
case BorderStyle.none:
break;
}
switch (bottom.style) {
case BorderStyle.solid:
paint.color = bottom.color;
path.reset();
path.moveTo(rect.right, rect.bottom);
path.lineTo(rect.left, rect.bottom);
if (bottom.width == 0.0) {
paint.style = PaintingStyle.stroke;
} else {
paint.style = PaintingStyle.fill;
path.lineTo(rect.left + left.width, rect.bottom - bottom.width);
path.lineTo(rect.right - right.width, rect.bottom - bottom.width);
}
canvas.drawPath(path, paint);
break;
case BorderStyle.none:
break;
}
switch (left.style) {
case BorderStyle.solid:
paint.color = left.color;
// path.reset();
// path.moveTo(rect.left, rect.bottom);
// path.lineTo(rect.left, rect.top);
if (left.width == 0.0) {
paint.style = PaintingStyle.stroke;
} else {
paint.style = PaintingStyle.fill;
// path.lineTo(rect.left + left.width, rect.top + top.width);
// path.lineTo(rect.left + left.width, rect.bottom - bottom.width);
}
// canvas.drawPath(path, paint);
paint.strokeWidth=2;
paint.color=Colors.green;
paint.strokeCap=StrokeCap.round;
List<Offset> pionts = [];
double h =rect.bottom- rect.top;
int interval = 10; // 间距
int length = (h / interval).toInt();
for (int i = 0; i < length; i++) {
pionts.add(Offset(23,interval*(i+2.5).toDouble()));
}
canvas.drawPoints(PointMode.points, pionts, paint);
break;
case BorderStyle.none:
break;
}
}
然后调整_detailView2代码:
// 单个日期具体行程
_detailView2(JourneyTimeLine timeLine) {
return Container(
// 有超出左边故外层加了Expanded【外层改为Stack,就要去掉Expanded】
alignment: Alignment.centerLeft,
margin: EdgeInsets.only(
left: 22,
top: 1,
right: 10,
),
padding: EdgeInsets.only(top: 0, bottom: 8, left: 13),
// color: Colors.blue[100],// 使用了decoration就不能使用这个属性了,报错:Cannot provide both a color and a decoratio
decoration: BoxDecoration(
border: PointBorder(left: BorderSide(color:Colors.greenAccent, width: 3)), // 左边边框
),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
children: timeLine.journeyList.map((Journey item) {
return Container(
alignment: Alignment.topLeft,
margin: EdgeInsets.only(right: 12, top: 9),
child: RichText(
// 富文本显示
text: TextSpan(children: [
TextSpan(
text: item.templateText,
style: TextStyle(
color: Colors.black87,
fontSize: 13,
),
),
TextSpan(
text: " " + item.place,
style: TextStyle(color: Colors.green, fontSize: 13),
recognizer: TapGestureRecognizer()
..onTap = () {
// 点击事件
// ToastUtils.showTs(item.keyText);
}),
]),
));
}).toList(),
),
);
}
可得效果图1