Flutter之ListView实现自动滑动到底部

6,445 阅读2分钟

这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战

使用场景

在常见社交App中,发送消息或者接收到他人消息后,消息列表都会自动滑动到底部,不需要我们手动滑动,这样的用户体验好。

思路

ListView使用ScrollController来控制滑动,其中有jumpToanimateTo2个方法滑动到指定的位置。

/// packages/flutter/lib/src/widgets/scroll_controller.dart

void jumpTo(double value){
    assert(_positions.isNotEmpty, 'ScrollController not attached to any scroll views.');
    for (final ScrollPosition position in List<ScrollPosition>.from(_positions))
      position.jumpTo(value);
}

Future<void> animateTo(
    double offset, {
      required Duration duration,
      required Curve curve,
    }) async {
    assert(_positions.isNotEmpty, 'ScrollController not attached to any scroll views.');
    await Future.wait<void>(<Future<void>>[
      for (int i = 0; i < _positions.length; i += 1) _positions[i].animateTo(offset, duration: duration, curve: curve),
    ]);
}

看命名就能看出来,jumpTo不带动画效果,animateTo能设置滑动动画。

第一个参数valueoffset都是代表ListView中的指定位置。

ScrollPostion

那么应该传入哪个值才是代表ListView的底部呢?

通过ScrollController2个方法的源码发现最终调用的是_positionjumpToanimateTo方法。

_postion是一个ScrollPostionScrollPostion中有2个变量minScrollExtentmaxScrollExtent,分别代表滑动到顶部、底部的位置。

最终可以使用以下代码实现滑动到底部

scrollController.jumpTo(scrollController.position.maxScrollExtent);

实现

布局

首先我们来定义一个简单的聊天界面。一个ListView,一个输入框,一个发送消息的按钮。

image.png

/// 消息列表
List<String> _messages = ["在干嘛","吃饭了吗","想你了","早点睡"];
TextEditingController _textEditingController = TextEditingController();
ScrollController _scrollController = ScrollController();

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('女神'),
    ),
    body: Column(
      children: [
        Expanded(
          child: ListView.builder(
            controller: _scrollController,
            itemBuilder: (context, index) {
              return SizedBox(
                child: Center(child: Text(_messages[index])),
                height: 50,
              );
            },
            itemCount: _messages.length,
          ),
        ),
        Row(
          children: [
            Expanded(
              child: Padding(
                padding: const EdgeInsets.symmetric(horizontal: 10),
                child: TextField(
                  controller: _textEditingController,
                  decoration: InputDecoration(
                    hintText: "请输入消息",
                  ),
                  textInputAction: TextInputAction.send,
                  onSubmitted: sendMessage,
                ),
              ),
            ),
            IconButton(
              onPressed: () {
                /// 发送消息
                sendMessage(_textEditingController.text.trim());
                /// 清除输入框中的内容
                _textEditingController.text = "";
              },
              icon: Icon(Icons.send),
            ),
          ],
        ),
        SizedBox(height: 5),
      ],
    ),
  );
}

滑动到底部

点击发送按钮时,获取输入框中的消息,添加到消息列表中,更新ui,然后滑动到底部。

void sendMessage(String message) {
  if (message.isEmpty) return;
  setState(() {
    _messages.add(message);
  });
  _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
}

问题

看起来没啥问题,但是运行发现,每次发送完消息后,列表并没有滑动到底部,而是滑动到了倒数第二次数据。

原因是执行滑动到底部的操作时,列表数据还没有更新,maxScrollExtent还是更新之前的值。

20210826_112101.gif

解决方案

那么我们需要等待setState刷新列表之后,再执行滑动操作。 所以我们可以使用延迟执行方案。一般延迟时间设置300到500毫秒即可。

void sendMessage(String message) {
  if (message.isEmpty) return;
  setState(() {
    _messages.add(message);
  });
  /// 延迟500毫秒,再进行滑动
  Future.delayed(Duration(milliseconds: 500), () {
    _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
  });
}

ok!大功告成,下面是演示效果。

20210826_120757.gif