使用场景
在常见社交App中,发送消息或者接收到他人消息后,消息列表都会自动滑动到底部,不需要我们手动滑动,这样的用户体验好。
思路
ListView使用ScrollController来控制滑动,其中有jumpTo
、animateTo
2个方法滑动到指定的位置。
/// 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
能设置滑动动画。
第一个参数value
和offset
都是代表ListView中的指定位置。
ScrollPostion
那么应该传入哪个值才是代表ListView的底部呢?
通过ScrollController
2个方法的源码发现最终调用的是_position
的jumpTo
、animateTo
方法。
_postion是一个ScrollPostion
,ScrollPostion
中有2个变量minScrollExtent
、maxScrollExtent
,分别代表滑动到顶部、底部的位置。
最终可以使用以下代码实现滑动到底部
scrollController.jumpTo(scrollController.position.maxScrollExtent);
实现
布局
首先我们来定义一个简单的聊天界面。一个ListView,一个输入框,一个发送消息的按钮。
/// 消息列表 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
还是更新之前的值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pucOH0Q3-1631608253352)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/308f11e872644472b83f78760105e2f3~tplv-k3u1fbpfcp-watermark.image)]
解决方案
那么我们需要等待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!大功告成,下面是演示效果。