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 with constraints 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.