'How can i implement this Timeline Ui in flutter
I want to implement a timeline ui like this
.
i tried by using listview builder and container inside transform widget for lines but i ended up getting large distance between those lines and circles and i can't find the correct angle.
is there any way to implement this ui. i need those circles clickable.
here is my implementation
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,
//
// ),
)
],
);
},),
),
);
Solution 1:[1]
For this case, I think ListView.separated will be the better choice. Above answer doesn't work after changing screen width.
We need to get the width of Chapter X and circle (having radius 24). To get the Text size, we will use this.
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;
}
You can check original question and answer of getting text size.
To draw lines I am using 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;
}
I am using ListView.builder with Stack[customPaint, widget] for this solution
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
100withconstraintsdynamic value
You can check full snippet and run on dartPad.
Also, if you are ok with not having precious positioning, you can use ListView.separate. You can find that inside dartPad, Also make sure to remove extra spacing on Path. You can simply replace path with drawLine.
Solution 2:[2]
i don't have a perfect solution for you, but you play around with my code (Use Stack instead of column)
Output :-
Code :-
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,
),
),
],
);
},
),
),
);
}
}
Solution 3:[3]
If you are not looking for specific UI then give a try with flutter package https://pub.dev/packages/timeline_tile
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Yeasin Sheikh |
| Solution 2 | MohammedAli |
| Solution 3 | user3376717 |



