我如何在 flutter 中实现这个时间轴 Ui
How can i implement this Timeline Ui in flutter
我想实现这样的时间线ui 。
我尝试使用 listview builder 和 container inside transform widget for lines 但我最终在这些线和圆圈之间得到了很大的距离,我找不到正确的角度。
有什么方法可以实现这个 ui。我需要那些可点击的圆圈。
这是我的实现
return Scaffold(
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 50),
child: ListView.builder(itemCount: 5,itemBuilder: (context, index) {
return Column(
children: [
Align(alignment: index%2 == 0? Alignment.centerLeft : Alignment.centerRight,child: Container(width: 50,height: 50,child: CustomPaint(painter: CirclePainter(),))),
index == 4 ? Container() : Transform.rotate(
angle: index%2 != 0?-angle: angle,
//-math.pi / 3.5
child: Container(height: 50,width: 1,color: Colors.black,)
// Container(
// height: 300,
// width: 1,
// color: Colors.black,
//
// ),
)
],
);
},),
),
);
这是我实现的(UI)
我没有适合你的完美解决方案,但你可以尝试一下我的代码(使用 Stack 而不是 column)
输出:-
代码:-
import 'package:flutter/material.dart';
class TimelineExample extends StatefulWidget {
const TimelineExample({Key? key}) : super(key: key);
@override
State<TimelineExample> createState() => _TimelineExampleState();
}
class _TimelineExampleState extends State<TimelineExample> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 50),
child: ListView.builder(
itemCount: 5,
itemBuilder: (context, index) {
return Stack(
alignment: Alignment.bottomCenter,
children: [
Align(
alignment: index % 2 == 0
? Alignment.centerLeft
: Alignment.centerRight,
child: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: Colors.teal,
borderRadius: BorderRadius.circular(50.0),
),
),
),
index == 4
? Container()
: Transform.rotate(
angle: index % 2 != 0 ? -0.3 : 0.3,
child: Container(
margin: const EdgeInsets.only(
left: 48.0,
right: 48.0,
),
height: 1,
color: Colors.black,
),
),
],
);
},
),
),
);
}
}
如果你不是在寻找特定的 UI 然后尝试使用 flutter 包 https://pub.dev/packages/timeline_tile
对于这种情况,我认为ListView.separated
会是更好的选择。以上答案在更改屏幕宽度后不起作用。
我们需要得到 Chapter X
和圆(半径为 24)的宽度。要获得 Text
大小,我们将使用它。
Size _textSize(String text, TextStyle style) {
final TextPainter textPainter = TextPainter(
text: TextSpan(text: text, style: style),
maxLines: 1,
textDirection: TextDirection.ltr)
..layout(minWidth: 0, maxWidth: double.infinity);
return textPainter.size;
}
您可以查看getting text size的原始问题和答案。
画线我使用CustomPainter
。
class DivPainter extends CustomPainter {
int index;
DivPainter({required this.index});
@override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()
..color = Colors.grey
..style = PaintingStyle.stroke
..strokeWidth = 5;
final path1 = Path()
..moveTo(0, 24)
..lineTo(size.width, size.height + 24);
final path2 = Path()
..moveTo(0, size.height + 24)
..lineTo(size.width, 24);
index.isEven
? canvas.drawPath(path1, paint)
: canvas.drawPath(path2, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
我将 ListView.builder
与 Stack[customPaint, widget]
一起用于此解决方案
LayoutBuilder listview() {
return LayoutBuilder(
builder: (context, constraints) => Container(
color: Colors.cyanAccent.withOpacity(.3),
width: constraints.maxWidth,
child: ListView.builder(
itemCount: itemLength,
itemBuilder: (context, index) {
final textWidth = _textSize("Chapter $index", textStyle).width;
final painterWidth = constraints.maxWidth -
((textWidth + 24) *
2); //24 for CircleAvatar, contains boths side
return SizedBox(
height: index == itemLength - 1 ? 24 * 2 : 100 + 24,
width: constraints.maxWidth,
child: Stack(
children: [
/// skip render for last item
if (index != itemLength - 1)
Align(
alignment: Alignment.center,
child: SizedBox(
width: painterWidth,
height: height,
child: CustomPaint(
painter: DivPainter(index: index),
),
),
),
Row(
mainAxisAlignment: index.isEven
? MainAxisAlignment.start
: MainAxisAlignment.end,
children: [
if (index.isEven)
Text(
"Chapter $index",
style: textStyle,
),
const CircleAvatar(radius: 24),
if (index.isOdd)
Text(
"Chapter $index",
style: textStyle,
),
],
),
],
),
);
},
),
),
);
}
make sure of screenWidth > lineHeight*2, you can replace 100
with constraints
dynamic value
您可以在 dartPad 上查看完整代码段和 运行。
另外,如果你不介意没有宝贵的定位,你可以使用 ListView.separate
。您可以在 dartPad 中找到它,同时确保删除 Path
上的多余间距。您可以简单地将路径替换为 drawLine
.
我想实现这样的时间线ui
我尝试使用 listview builder 和 container inside transform widget for lines 但我最终在这些线和圆圈之间得到了很大的距离,我找不到正确的角度。
有什么方法可以实现这个 ui。我需要那些可点击的圆圈。
这是我的实现
return Scaffold(
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 50),
child: ListView.builder(itemCount: 5,itemBuilder: (context, index) {
return Column(
children: [
Align(alignment: index%2 == 0? Alignment.centerLeft : Alignment.centerRight,child: Container(width: 50,height: 50,child: CustomPaint(painter: CirclePainter(),))),
index == 4 ? Container() : Transform.rotate(
angle: index%2 != 0?-angle: angle,
//-math.pi / 3.5
child: Container(height: 50,width: 1,color: Colors.black,)
// Container(
// height: 300,
// width: 1,
// color: Colors.black,
//
// ),
)
],
);
},),
),
);
这是我实现的(UI)
我没有适合你的完美解决方案,但你可以尝试一下我的代码(使用 Stack 而不是 column)
输出:-
代码:-
import 'package:flutter/material.dart';
class TimelineExample extends StatefulWidget {
const TimelineExample({Key? key}) : super(key: key);
@override
State<TimelineExample> createState() => _TimelineExampleState();
}
class _TimelineExampleState extends State<TimelineExample> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 50),
child: ListView.builder(
itemCount: 5,
itemBuilder: (context, index) {
return Stack(
alignment: Alignment.bottomCenter,
children: [
Align(
alignment: index % 2 == 0
? Alignment.centerLeft
: Alignment.centerRight,
child: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: Colors.teal,
borderRadius: BorderRadius.circular(50.0),
),
),
),
index == 4
? Container()
: Transform.rotate(
angle: index % 2 != 0 ? -0.3 : 0.3,
child: Container(
margin: const EdgeInsets.only(
left: 48.0,
right: 48.0,
),
height: 1,
color: Colors.black,
),
),
],
);
},
),
),
);
}
}
如果你不是在寻找特定的 UI 然后尝试使用 flutter 包 https://pub.dev/packages/timeline_tile
对于这种情况,我认为ListView.separated
会是更好的选择。以上答案在更改屏幕宽度后不起作用。
我们需要得到 Chapter X
和圆(半径为 24)的宽度。要获得 Text
大小,我们将使用它。
Size _textSize(String text, TextStyle style) {
final TextPainter textPainter = TextPainter(
text: TextSpan(text: text, style: style),
maxLines: 1,
textDirection: TextDirection.ltr)
..layout(minWidth: 0, maxWidth: double.infinity);
return textPainter.size;
}
您可以查看getting text size的原始问题和答案。
画线我使用CustomPainter
。
class DivPainter extends CustomPainter {
int index;
DivPainter({required this.index});
@override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()
..color = Colors.grey
..style = PaintingStyle.stroke
..strokeWidth = 5;
final path1 = Path()
..moveTo(0, 24)
..lineTo(size.width, size.height + 24);
final path2 = Path()
..moveTo(0, size.height + 24)
..lineTo(size.width, 24);
index.isEven
? canvas.drawPath(path1, paint)
: canvas.drawPath(path2, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
我将 ListView.builder
与 Stack[customPaint, widget]
一起用于此解决方案
LayoutBuilder listview() {
return LayoutBuilder(
builder: (context, constraints) => Container(
color: Colors.cyanAccent.withOpacity(.3),
width: constraints.maxWidth,
child: ListView.builder(
itemCount: itemLength,
itemBuilder: (context, index) {
final textWidth = _textSize("Chapter $index", textStyle).width;
final painterWidth = constraints.maxWidth -
((textWidth + 24) *
2); //24 for CircleAvatar, contains boths side
return SizedBox(
height: index == itemLength - 1 ? 24 * 2 : 100 + 24,
width: constraints.maxWidth,
child: Stack(
children: [
/// skip render for last item
if (index != itemLength - 1)
Align(
alignment: Alignment.center,
child: SizedBox(
width: painterWidth,
height: height,
child: CustomPaint(
painter: DivPainter(index: index),
),
),
),
Row(
mainAxisAlignment: index.isEven
? MainAxisAlignment.start
: MainAxisAlignment.end,
children: [
if (index.isEven)
Text(
"Chapter $index",
style: textStyle,
),
const CircleAvatar(radius: 24),
if (index.isOdd)
Text(
"Chapter $index",
style: textStyle,
),
],
),
],
),
);
},
),
),
);
}
make sure of screenWidth > lineHeight*2, you can replace
100
withconstraints
dynamic value
您可以在 dartPad 上查看完整代码段和 运行。
另外,如果你不介意没有宝贵的定位,你可以使用 ListView.separate
。您可以在 dartPad 中找到它,同时确保删除 Path
上的多余间距。您可以简单地将路径替换为 drawLine
.