Reputation: 88
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,
//
// ),
)
],
);
},),
),
);
Heres what ive implemented(UI)
Upvotes: 1
Views: 933
Reputation: 63709
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
100
withconstraints
dynamic 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
.
Upvotes: 1
Reputation: 11
If you are not looking for specific UI then give a try with flutter package https://pub.dev/packages/timeline_tile
Upvotes: 0
Reputation: 2519
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,
),
),
],
);
},
),
),
);
}
}
Upvotes: 1