如何实现仅当用户处于ListView底部时,ScrollController才自动滚到底部?
实现类似Facebook聊天的ListView自动滚动逻辑
问题描述
我用ScrollController配合ListView.builder实现列表,现在每次添加元素都会自动滚到底部。想要改成类似Facebook聊天的效果:只有当用户当前处于ListView底部时,才自动滚动到底部。
我知道可以通过scrollController.position.pixels获取当前滚动位置,scrollController.position.maxScrollExtent获取列表总高度,原本想通过判断当前位置 + 最后一个元素高度 == 最大滚动范围来实现,但不知道怎么获取最后一个元素的高度,请问该怎么做?
现有代码
import 'dart:io'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; class StartProcessWidget extends HookWidget { final ScrollController scrollController = ScrollController(); StartProcessWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { var ouputs = useState<List<String>>([]); scrollDown() { if (scrollController.hasClients) { scrollController.jumpTo(scrollController.position.maxScrollExtent); } } String generateRandomString(int len) { var r = Random(); return String.fromCharCodes(List.generate(len, (index) => r.nextInt(33) + 89)); } addElements() { for (var i = 0; i < 1000; i++) { ouputs.value = [...ouputs.value, generateRandomString(Random().nextInt(100))]; sleep(const Duration(milliseconds: 500)); } } useEffect(() { addElements(); }, []); useEffect(() { scrollDown(); }, [ouputs.value]); return Container( height: 500, child: ListView.builder( controller: scrollController, itemCount: ouputs.value.length, shrinkWrap: true, prototypeItem: SelectableText(ouputs.value.first), itemBuilder: (context, index) { return SelectableText(ouputs.value[index]); }, ), ); } }
解决方案
不需要单独获取最后一个元素的高度,直接利用ScrollPosition的内置属性或设置阈值判断即可,具体实现如下:
方法一:严格判断是否处于底部
利用atEdge属性结合滚动位置,精准判断用户是否处于列表底部:
scrollDown() { if (scrollController.hasClients) { // atEdge为true时,判断当前位置是否等于最大滚动范围(即底部) final isAtBottom = scrollController.position.atEdge && scrollController.position.pixels == scrollController.position.maxScrollExtent; if (isAtBottom) { scrollController.jumpTo(scrollController.position.maxScrollExtent); // 若需要平滑滚动,可替换为: // scrollController.animateTo( // scrollController.position.maxScrollExtent, // duration: const Duration(milliseconds: 200), // curve: Curves.easeOut, // ); } } }
方法二:宽松判断接近底部
如果担心严格判断过于敏感(用户轻微滚动就停止触发),可以设置一个阈值,当距离底部在阈值范围内时自动滚动:
scrollDown() { if (scrollController.hasClients) { const double threshold = 20.0; // 距离底部20px以内视为处于底部区域 final isNearBottom = scrollController.position.maxScrollExtent - scrollController.position.pixels <= threshold; if (isNearBottom) { scrollController.jumpTo(scrollController.position.maxScrollExtent); } } }
额外优化:修复UI阻塞问题
原代码中addElements用sleep会阻塞UI线程导致卡顿,建议改为异步延迟实现:
Future<void> addElements() async { for (var i = 0; i < 1000; i++) { await Future.delayed(const Duration(milliseconds: 500)); ouputs.value = [...ouputs.value, generateRandomString(Random().nextInt(100))]; } } // 对应修改useEffect: useEffect(() { addElements(); return null; }, []);
内容的提问来源于stack exchange,提问作者Lenny4
相关产品推荐
相关产品推荐

