Flutter: Create a timeline UI Flutter: Create a timeline UI dart dart

Flutter: Create a timeline UI


I like Osama's answer too but here's my quick custom implementation. It uses a CustomPainter to draw the lines.

import 'package:flutter/material.dart';class Timeline extends StatelessWidget {  const Timeline({    Key? key,    required this.children,    this.indicators,    this.isLeftAligned = true,    this.itemGap = 12.0,    this.gutterSpacing = 4.0,    this.padding = const EdgeInsets.all(8),    this.controller,    this.lineColor = Colors.grey,    this.physics,    this.shrinkWrap = true,    this.primary = false,    this.reverse = false,    this.indicatorSize = 30.0,    this.lineGap = 4.0,    this.indicatorColor = Colors.blue,    this.indicatorStyle = PaintingStyle.fill,    this.strokeCap = StrokeCap.butt,    this.strokeWidth = 2.0,    this.style = PaintingStyle.stroke,  })  : itemCount = children.length,        assert(itemGap >= 0),        assert(lineGap >= 0),        assert(indicators == null || children.length == indicators.length),        super(key: key);  final List<Widget> children;  final double itemGap;  final double gutterSpacing;  final List<Widget>? indicators;  final bool isLeftAligned;  final EdgeInsets padding;  final ScrollController? controller;  final int itemCount;  final ScrollPhysics? physics;  final bool shrinkWrap;  final bool primary;  final bool reverse;  final Color lineColor;  final double lineGap;  final double indicatorSize;  final Color indicatorColor;  final PaintingStyle indicatorStyle;  final StrokeCap strokeCap;  final double strokeWidth;  final PaintingStyle style;  @override  Widget build(BuildContext context) {    return ListView.separated(      padding: padding,      separatorBuilder: (_, __) => SizedBox(height: itemGap),      physics: physics,      shrinkWrap: shrinkWrap,      itemCount: itemCount,      controller: controller,      reverse: reverse,      primary: primary,      itemBuilder: (context, index) {        final child = children[index];        final _indicators = indicators;        Widget? indicator;        if (_indicators != null) {          indicator = _indicators[index];        }        final isFirst = index == 0;        final isLast = index == itemCount - 1;        final timelineTile = <Widget>[          CustomPaint(            foregroundPainter: _TimelinePainter(              hideDefaultIndicator: indicator != null,              lineColor: lineColor,              indicatorColor: indicatorColor,              indicatorSize: indicatorSize,              indicatorStyle: indicatorStyle,              isFirst: isFirst,              isLast: isLast,              lineGap: lineGap,              strokeCap: strokeCap,              strokeWidth: strokeWidth,              style: style,              itemGap: itemGap,            ),            child: SizedBox(              height: double.infinity,              width: indicatorSize,              child: indicator,            ),          ),          SizedBox(width: gutterSpacing),          Expanded(child: child),        ];        return IntrinsicHeight(          child: Row(            mainAxisAlignment: MainAxisAlignment.start,            children:                isLeftAligned ? timelineTile : timelineTile.reversed.toList(),          ),        );      },    );  }}class _TimelinePainter extends CustomPainter {  _TimelinePainter({    required this.hideDefaultIndicator,    required this.indicatorColor,    required this.indicatorStyle,    required this.indicatorSize,    required this.lineGap,    required this.strokeCap,    required this.strokeWidth,    required this.style,    required this.lineColor,    required this.isFirst,    required this.isLast,    required this.itemGap,  })  : linePaint = Paint()          ..color = lineColor          ..strokeCap = strokeCap          ..strokeWidth = strokeWidth          ..style = style,        circlePaint = Paint()          ..color = indicatorColor          ..style = indicatorStyle;  final bool hideDefaultIndicator;  final Color indicatorColor;  final PaintingStyle indicatorStyle;  final double indicatorSize;  final double lineGap;  final StrokeCap strokeCap;  final double strokeWidth;  final PaintingStyle style;  final Color lineColor;  final Paint linePaint;  final Paint circlePaint;  final bool isFirst;  final bool isLast;  final double itemGap;  @override  void paint(Canvas canvas, Size size) {    final indicatorRadius = indicatorSize / 2;    final halfItemGap = itemGap / 2;    final indicatorMargin = indicatorRadius + lineGap;    final top = size.topLeft(Offset(indicatorRadius, 0.0 - halfItemGap));    final centerTop = size.centerLeft(      Offset(indicatorRadius, -indicatorMargin),    );    final bottom = size.bottomLeft(Offset(indicatorRadius, 0.0 + halfItemGap));    final centerBottom = size.centerLeft(      Offset(indicatorRadius, indicatorMargin),    );    if (!isFirst) canvas.drawLine(top, centerTop, linePaint);    if (!isLast) canvas.drawLine(centerBottom, bottom, linePaint);    if (!hideDefaultIndicator) {      final Offset offsetCenter = size.centerLeft(Offset(indicatorRadius, 0));      canvas.drawCircle(offsetCenter, indicatorRadius, circlePaint);    }  }  @override  bool shouldRepaint(CustomPainter oldDelegate) {    return false;  }}

You'd call it something like:

Timeline(  children: <Widget>[    Container(height: 100, color: color),    Container(height: 50, color: color),    Container(height: 200, color: color),    Container(height: 100, color: color),  ],  indicators: <Widget>[    Icon(Icons.access_alarm),    Icon(Icons.backup),    Icon(Icons.accessibility_new),    Icon(Icons.access_alarm),  ],),

An Image of a timeline mobile ui


 new ListView.builder(                  itemBuilder: (BuildContext context, int index) {                    return new Stack(                      children: <Widget>[                        new Padding(                          padding: const EdgeInsets.only(left: 50.0),                          child: new Card(                            margin: new EdgeInsets.all(20.0),                            child: new Container(                              width: double.infinity,                              height: 200.0,                              color: Colors.green,                            ),                          ),                        ),                        new Positioned(                          top: 0.0,                          bottom: 0.0,                          left: 35.0,                          child: new Container(                            height: double.infinity,                            width: 1.0,                            color: Colors.blue,                          ),                        ),                        new Positioned(                          top: 100.0,                          left: 15.0,                          child: new Container(                            height: 40.0,                            width: 40.0,                            decoration: new BoxDecoration(                              shape: BoxShape.circle,                              color: Colors.white,                            ),                            child: new Container(                              margin: new EdgeInsets.all(5.0),                              height: 30.0,                              width: 30.0,                              decoration: new BoxDecoration(                                  shape: BoxShape.circle,                                  color: Colors.red),                            ),                          ),                        )                      ],                    );                  },                  itemCount: 5,                )

the output will be like this image enter image description here


For those who are scrolling here to find an easy way to implement timelines, now you can do this easily with timeline_tile.

Check out this specific delivery layout:

Or this weather timeline:

Also, the beautiful_timelines repository contains some examples built with this package.

Web demo