Flutter 多标签切换自动滚动居中

1,081 阅读1分钟

一般项目中都有这种多标签页的情况,正常使用 ListView.builder 构建横向滚动的视图时,通过绑定的 controller 可以控制 listView 的偏移量,但是如果子控件长短不一致,计算起来就很麻烦了。

file.gif

我们可以通过给每一个子 widget 绑定 GlobalKey,并保存到数组中,在点击切换的时候,获取到点击控件对应的 key,再调用 Scrollable.ensureVisiable 方法就能很方便的实现上述效果。

完整代码如下

class _HomePageState extends State<HomePage> {
  List<String> titles = [];
  List<GlobalKey> itemKeys = [];
  int selectedIndex = 0;

  @override
  void initState() {
    List<String> res = [];
    for (var i = 0; i < 10; i++) {
      res.add('index-$i');
      itemKeys.add(GlobalKey());
    }
    setState(() {
      titles = res;
    });
    super.initState();
  }

  void scrollToItem(int index) {
    final context = itemKeys[index].currentContext!;
    Scrollable.ensureVisible(
      context,
      alignment: 0.5,  // 控制出现的位置
      duration: const Duration(milliseconds: 300),  // 动画时长
    );
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: SizedBox(
        height: 32,
        child: ListView.builder(
          scrollDirection: Axis.horizontal,
          padding: const EdgeInsets.symmetric(horizontal: 20),
          itemCount: titles.length,
          itemBuilder: (context, index) {
            return Container(
              key: itemKeys[index],
              decoration: BoxDecoration(
                color: selectedIndex == index ? Colors.blue : Colors.grey,
                borderRadius: const BorderRadius.all(Radius.circular(15)),
              ),
              padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
              margin: const EdgeInsets.symmetric(horizontal: 5),
              child: GestureDetector(
                onTap: () {
                  setState(() {
                    selectedIndex = index;
                  });
                  scrollToItem(index);
                },
                child: Text(
                  titles[index],
                  style: TextStyle(
                      color: selectedIndex == index
                          ? Colors.white
                          : Colors.grey.shade50),
                ),
              ),
            );
          },
        ),
      ),
    );
  }
}