Flutter 时间线控件

3,147 阅读3分钟

前言

最近因为业务需要TimeLine组件用来显示时间线,看了部分框架和对应源码后决定自己根据自己的业务需求封装一个简单。本篇文章使用Column/Row实现,实现TimeLine组件方式有很多,本篇文章不会全部涉及到,最后封装的组件如果你需要进行使用,可以根据自己的需求在多填写部分参数即可。

最终效果

参考效果最后效果
目标效果.png

思路分析

在制作这个组件的时候不需要一次性想着全部做完,把一个完整的组件拆分成一个个小组件,最后组合起来即可,同时我们还需要注意组件的不同状态下的样式。

image.png 分析单个时间线你会发现其实很简单,左边是一个圆点以及圆点下面的实线,右边就是对应信息,当当前时间线是最后一个圆点下面的实线应当隐藏

了解完了这些基本信息就知道需要组件有哪些了,列举一下单个时间线的控件结构就是这样

Row(
    Column(
        Container()
        Expanded(
            Container()
        )
    )
    Expanded(
       Column(
           Text()
           Text()
       )
    )
)

现在我们就可以开始实现了,单个制作出来以后,如果需要多个就直接丢进ListView即可

开始实现

因为有时候TimeLine不需要显示实线,所以我们用一个bool值标识即可,最后在布局的时候判断一下即可,代码就是这样

     
class TimeLineWidget extends StatelessWidget {
  const TimeLineWidget({
    super.key,
    this.isLast = false,
    required this.child,
  });
  final bool isLast;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Column(
          children: [
            Container(
              width: 24,
              height: 24,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                color: Colors.green.shade700,
              ),
            ),
            if (isLast)
              const SizedBox.shrink()
            else
              Expanded(
                child: Container(
                  width: .5,
                  color: Colors.green.shade500,
                ),
              ),
          ],
        ),
        const SizedBox(width: 16),
        Expanded(child: child),
      ],
    );
  }
}

这样就做完了单个TimeLine,需要多个就可以直接丢到ListView中,但是现在的控件在放进ListView里渲染之前,还需要处理一个边界问题。因为我们的实线是根据当前右侧控件的高度来进行计算,所以我们直接使用Expanded没有问题,Expanded是会将父控件的布局剩余高度传给Child。所以这个代码放在Column里面运行起来自然是没问题,但是放到了无边界的ListView中就会报错,解决的办法就是自己测量自身高度,然后让Expanded获取即可。所以还需要套一个IntrinsicHeight组件,这个组建的作用就是获取子组件的高度。这样就不会出现边界问题。最后最终代码

TimeLineWidget


class TimeLineWidget extends StatelessWidget {
  const TimeLineWidget({
    super.key,
    this.isLast = false,
    required this.child,
  });
  final bool isLast;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return IntrinsicHeight(
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Column(
            children: [
              Container(
                width: 24,
                height: 24,
                decoration: BoxDecoration(
                  shape: BoxShape.circle,
                  color: Colors.green.shade700,
                ),
              ),
              if (isLast)
                const SizedBox.shrink()
              else
                Expanded(
                  child: Container(
                    width: .5,
                    color: Colors.green.shade500,
                  ),
                ),
            ],
          ),
          const SizedBox(width: 16),
          Expanded(child: child),
        ],
      ),
    );
  }
}

现在就可以使用了

ListView.builder(
      itemCount: 8,
      itemBuilder: (context, index) {
        return TimeLineWidget(
          isLast: index == 7,
          child: Container(
            margin: const EdgeInsets.only(bottom: 24),
            child: index == 4
                ? Container(
                    height: 100,
                    decoration: BoxDecoration(
                      color: Colors.green.shade700,
                      borderRadius: BorderRadius.circular(8),
                    ),
                  )
                : Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        "Title $index",
                        style: const TextStyle(fontSize: 20),
                      ),
                      Text(
                        "SubTitle $index",
                        style:
                            const TextStyle(fontSize: 16, color: Colors.grey),
                      ),
                    ],
                  ),
          ),
        );
      },
    );

最后效果

源码

结尾

如果看完文章还不是很能理解的话,可以看我对于这个控件录制的相关视频,最后,实现这个控件的方式有很多,Canvas,Stack等都可以。欢迎交流,最后感谢观看