skip to Main Content

How to create Custom Timelime in flutter like bellow:

enter image description here

This is the code i have tried so far:

IntrinsicHeight(
    child: Row(
      children: [
        Container(
          width: 8.w,
          height: double.infinity,
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(16.r),
            gradient: const LinearGradient(
              begin: Alignment.topCenter,
              end: Alignment.bottomCenter,
              colors: [
                Color(0XFF402883),
                Color(0XFF6A55EA),
                Color(0XFF8F84D2),
                Color(0XFFADA6D5),
              ],
            ),
          ),
        ),
        SizedBox(width: 10.w),
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                "Sign Up",
                style: TextStyle(
                  fontSize: 14.sp,
                  fontWeight: FontWeight.w700,
                ),
              ),
              Text(
                "Create an account to invest & Trade",
                style: TextStyle(
                  fontSize: 12.sp,
                  fontWeight: FontWeight.w400,
                ),
              ),
              SizedBox(height: 10.h),
              Text(
                "Investment Order Submitted",
                style: TextStyle(
                  fontSize: 14.sp,
                  fontWeight: FontWeight.w700,
                ),
              ),
              Text(
                "You will be charged when the company's Escrow account opens(if it hasn't already)",
                style: TextStyle(
                  fontSize: 12.sp,
                  fontWeight: FontWeight.w400,
                ),
              ),
              SizedBox(height: 10.h),
              Text(
                "Funds In Transit",
                style: TextStyle(
                  fontSize: 14.sp,
                  fontWeight: FontWeight.w700,
                ),
              ),
              Text(
                "Your Payment method has been charged,funds should arrive in escrow in 3-5 business days.",
                style: TextStyle(
                  fontSize: 12.sp,
                  fontWeight: FontWeight.w400,
                ),
              ),
              SizedBox(height: 10.h),
              Text(
                "Funds Received",
                style: TextStyle(
                  fontSize: 14.sp,
                  fontWeight: FontWeight.w700,
                ),
              ),
              Text(
                "Your investment finalization occurs at campaign close, potentially earlier with disbursement.",
                style: TextStyle(
                  fontSize: 12.sp,
                  fontWeight: FontWeight.w400,
                ),
              ),
              SizedBox(height: 10.h),
              Text(
                "Funds Invested",
                style: TextStyle(
                  fontSize: 14.sp,
                  fontWeight: FontWeight.w700,
                ),
              ),
              Text(
                "Your investment is complete and can find the details and ownership docs in your portfolio.",
                style: TextStyle(
                  fontSize: 12.sp,
                  fontWeight: FontWeight.w400,
                ),
              ),
            ],
          ),
        ),
      ],
    ),
  )

I have acheived this UI with above code:

enter image description here

Now to add these dots on the vertical line which is perfectly aligned with the title in front of it.

Thanks.

2

Answers


  1. (Keep in mind that I removed 10.sp 12.w 8.w and other things like this, so don’t forget to add them in your code)

    Because the first item description is only 1. line it is hard to set foxed height
    Use this code:

     IntrinsicHeight(
                child: Row(
                  children: [
                    Container(
                      width: 8,
                      height: double.infinity,
                      decoration: BoxDecoration(
                        borderRadius: BorderRadius.circular(16),
                        gradient: const LinearGradient(
                          begin: Alignment.topCenter,
                          end: Alignment.bottomCenter,
                          colors: [
                            Color(0XFF402883),
                            Color(0XFF6A55EA),
                            Color(0XFF8F84D2),
                            Color(0XFFADA6D5),
                          ],
                        ),
                      ),
                      child: Container(
                        height: 100,
                        margin: const EdgeInsets.only(top: 6),
                        child: ListView.builder(
                          shrinkWrap: true,
                          itemCount: 5,
                          itemBuilder: (context, index) => Container(
                            margin: EdgeInsets.only(
                                top: index == 0
                                    ? 2
                                    : index == 1
                                        ? 51
                                        : index + 6 + 52,
                                right: 2,
                                left: 2),
                            height: 4,
                            decoration: BoxDecoration(
                                borderRadius: BorderRadius.circular(100),
                                color: Colors.white),
                          ),
                        ),
                      ),
                    ),
                    const SizedBox(width: 10),
                    const Expanded(
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          SizedBox(
                            height: 40,
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Text(
                                  "Sign Up",
                                  style: TextStyle(
                                    fontSize: 14,
                                    fontWeight: FontWeight.w700,
                                  ),
                                ),
                                Text(
                                  "Create an account to invest & Trade",
                                  style: TextStyle(
                                    fontSize: 12,
                                    fontWeight: FontWeight.w400,
                                  ),
                                ),
                              ],
                            ),
                          ),
                          SizedBox(height: 14),
                          SizedBox(
                            height: 55,
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Text(
                                  "Investment Order Submitted",
                                  style: TextStyle(
                                    fontSize: 14,
                                    fontWeight: FontWeight.w700,
                                  ),
                                ),
                                Text(
                                  "You will be charged when the company's Escrow account opens(if it hasn't already)",
                                  style: TextStyle(
                                    fontSize: 12,
                                    fontWeight: FontWeight.w400,
                                  ),
                                ),
                              ],
                            ),
                          ),
                          SizedBox(height: 10),
                          SizedBox(
                            height: 55,
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Text(
                                  "Funds In Transit",
                                  style: TextStyle(
                                    fontSize: 14,
                                    fontWeight: FontWeight.w700,
                                  ),
                                ),
                                Text(
                                  "Your Payment method has been charged,funds should arrive in escrow in 3-5 business days.",
                                  style: TextStyle(
                                    fontSize: 12,
                                    fontWeight: FontWeight.w400,
                                  ),
                                ),
                              ],
                            ),
                          ),
                          SizedBox(height: 10),
                          SizedBox(
                            height: 55,
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Text(
                                  "Funds Received",
                                  style: TextStyle(
                                    fontSize: 14,
                                    fontWeight: FontWeight.w700,
                                  ),
                                ),
                                Text(
                                  "Your investment finalization occurs at campaign close, potentially earlier with disbursement.",
                                  style: TextStyle(
                                    fontSize: 12,
                                    fontWeight: FontWeight.w400,
                                  ),
                                ),
                              ],
                            ),
                          ),
                          SizedBox(height: 10),
                          SizedBox(
                            height: 55,
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Text(
                                  "Funds Invested",
                                  style: TextStyle(
                                    fontSize: 14,
                                    fontWeight: FontWeight.w700,
                                  ),
                                ),
                                Text(
                                  "Your investment is complete and can find the details and ownership docs in your portfolio.",
                                  style: TextStyle(
                                    fontSize: 12,
                                    fontWeight: FontWeight.w400,
                                  ),
                                ),
                              ],
                            ),
                          ),
                        ],
                      ),
                    ),
                  ],
                ),
              ),
    

    Here is the result:
    enter image description here

    Login or Signup to reply.
  2. IntrinsicHeight class is relatively expensive. Avoid using it where possible, like in this case for example. See Flutter’s own documentation on this.

    You can achieve the same result without sacrificing performance using other native Flutter components. Here is a complete example:

    Code:

    import 'dart:math';
    
    import 'package:flutter/material.dart';
    
    void main() {
      runApp(const MainApp());
    }
    
    class MainApp extends StatelessWidget {
      const MainApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          theme: ThemeData(
            textTheme: Theme.of(context).textTheme.apply(
                  bodyColor: Colors.white,
                  displayColor: Colors.white,
                ),
          ),
          home: const TimelinePage(),
        );
      }
    }
    
    class TimelinePage extends StatelessWidget {
      const TimelinePage({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          backgroundColor: Colors.black,
          body: Center(
            child: SafeArea(
              child: Padding(
                padding: const EdgeInsets.symmetric(horizontal: 12),
                child: Timeline(
                  timelineTiles: [
                    TimelineTile(
                      title: "Sign Up",
                      description: "Create an account to invest & Trade",
                    ),
                    TimelineTile(
                      title: "Investment Order Submitted",
                      description:
                          "You will be charged when the company's Escrow account opens(if it hasn't already)",
                    ),
                    TimelineTile(
                      title: "Funds In Transit",
                      description:
                          "Your Payment method has been charged,funds should arrive in escrow in 3-5 business days.",
                    ),
                    TimelineTile(
                      title: "Funds Received",
                      description:
                          "Your investment finalization occurs at campaign close, potentially earlier with disbursement.",
                    ),
                    TimelineTile(
                      title: "Funds Invested",
                      description:
                          "Your investment is complete and can find the details and ownership docs in your portfolio.",
                    ),
                  ],
                ),
              ),
            ),
          ),
        );
      }
    }
    
    class Timeline extends StatefulWidget {
      const Timeline({
        super.key,
        required this.timelineTiles,
        this.sliderWidth = 12,
        this.circleSize = 8,
        this.gapBetweenSliderAndText = 6,
        this.gapTimelineTile = 12,
        this.sliderDecoration,
        this.hasAnimation = true,
      });
    
      final List<TimelineTile> timelineTiles;
      final double sliderWidth;
      final double circleSize;
      final double gapBetweenSliderAndText;
      final double gapTimelineTile;
      final BoxDecoration? sliderDecoration;
      final bool hasAnimation;
    
      @override
      State<Timeline> createState() => _TimelineState();
    }
    
    class _TimelineState extends State<Timeline> {
      double? widgetHeight;
      GlobalKey textColumnKey = GlobalKey();
    
      @override
      void initState() {
        super.initState();
        WidgetsBinding.instance.addPostFrameCallback((_) {
          if (mounted) calculateHeight();
        });
      }
    
      void calculateHeight() {
        if (textColumnKey.currentContext != null) {
          RenderBox renderBox =
              textColumnKey.currentContext!.findRenderObject() as RenderBox;
          setState(() => widgetHeight = renderBox.size.height);
        }
      }
    
      @override
      void didUpdateWidget(covariant Timeline oldWidget) {
        super.didUpdateWidget(oldWidget);
        WidgetsBinding.instance.addPostFrameCallback((_) {
          if (mounted) calculateHeight();
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Stack(
          children: [
            AnimatedContainer(
              duration: widget.hasAnimation
                  ? const Duration(milliseconds: 300)
                  : Duration.zero,
              curve: Curves.decelerate,
              width: widget.sliderWidth,
              height: widgetHeight ?? 0,
              margin: EdgeInsets.only(
                left: widget.sliderWidth < widget.circleSize
                    ? (widget.circleSize - widget.sliderWidth) / 2
                    : 0,
              ),
              decoration: widget.sliderDecoration ??
                  BoxDecoration(
                    borderRadius: BorderRadius.circular(16),
                    gradient: const LinearGradient(
                      begin: Alignment.topCenter,
                      end: Alignment.bottomCenter,
                      colors: [
                        Color(0XFF402883),
                        Color(0XFF6A55EA),
                        Color(0XFF8F84D2),
                        Color(0XFFADA6D5),
                      ],
                    ),
                  ),
            ),
            Column(
              key: textColumnKey,
              crossAxisAlignment: CrossAxisAlignment.start,
              mainAxisSize: MainAxisSize.min,
              children: List.generate(
                widget.timelineTiles.length,
                (index) => Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      children: [
                        SizedBox(
                          width:
                              max((widget.sliderWidth - widget.circleSize) / 2, 0),
                        ),
                        Container(
                          width: widget.circleSize,
                          height: widget.circleSize,
                          decoration: const BoxDecoration(
                            color: Colors.white,
                            shape: BoxShape.circle,
                          ),
                        ),
                        SizedBox(
                          width:
                              max((widget.sliderWidth - widget.circleSize) / 2, 0),
                        ),
                        SizedBox(width: widget.gapBetweenSliderAndText),
                        Text(
                          widget.timelineTiles[index].title,
                          style: const TextStyle(
                            fontSize: 14,
                            fontWeight: FontWeight.w700,
                          ),
                        ),
                      ],
                    ),
                    Padding(
                      padding: EdgeInsets.only(
                        left: max(widget.sliderWidth, widget.circleSize) +
                            widget.gapBetweenSliderAndText,
                        bottom: widget.timelineTiles.length - 1 == index
                            ? 0
                            : widget.gapTimelineTile,
                      ),
                      child: Text(
                        widget.timelineTiles[index].description,
                        style: const TextStyle(
                          fontSize: 12,
                          fontWeight: FontWeight.w400,
                        ),
                      ),
                    )
                  ],
                ),
              ),
            ),
          ],
        );
      }
    }
    
    class TimelineTile {
      final String title;
      final String description;
    
      TimelineTile({
        required this.title,
        required this.description,
      });
    }
    

    Note that in this example you can change the spacing and even the layout of the elements as you prefer.

    You just change the timelineTiles list inside Timeline using your state controller and generate as the data is provided by the API. I took the liberty of adding a slight animation to ensure a better visual experience.

    Outputs:

    Same example as the question
    or
    Alternative layout

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search