Flutter ListView优雅的获取第一个可见Item的Position

本文介绍了一种在Flutter中确定ListView首个可见Item位置的方法。通过跟踪Item构建过程和使用NotificationListener,作者实现了对可见Item范围的精准定位。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

      Flutter给我们提供了丰富的控件和控制方法,但是也有不少问题,就这两天,我就遇到去获取ListView第一个可见Item的Position的问题,Flutter并没有提供方法,只能我们想办法,我记得有大佬说过ListView和Android的RecyleView一样,那ListView的ItemBuilder肯定是用多少创建多少,用哪个创建哪个,抱着试试的态度,我在ItemBuilder打印了一下,果然,他只创建当前要用的Item,下来我们看看我的思路

     我们首先考虑初始化了多少个Item,这个信息对我们又什么用呢?? 这个数据我们缓存成Items的位置数组“positions”,(例如初始化Item数量为 5  ,表示初始化了0-5  Position 的 Item),初始化完成后,ListView滑动时,builder会根据需要创建新的Item,回收不用的Item。 builder每添加一个Item(前面说过,用一个添加一个,已经初始化好的Item,builder不在创建,即不再执行创建方法),我们就在初始化时缓存的位置变量“positions”的基础上改动,那Position 数组就可以自行调整头和尾了。

       这样,我们就知道大概第一个可见Item就在数组变量“positions”的前几个,为什么说是前几个,而不是第一个,因为我们缓存的Position 包括 屏幕可见+缓存的,所以第一个并不一定时可见的。

       好了,讲了那么多,那怎么去知道初始化了多少个Item??? 在初始化时 ,我们不确定到底会 初始创建 多少Item,数组变量也不好创建, 所以我们用一个整形变量“memoryPosition”去记录 初始化创建 多少Item,好了,我们现在知道Item数量了(包括 屏幕可见+缓存的)。      初始化Item的数量我们知道了。接下来 我们找个合适的时机将Item数量 Position 缓存成List数组—>“positions”,我是在NotificationListener的ScroStartNotification里面缓存Position的。    positions数组可以自行调整头和尾 那不就更简单了  ,看代码 ↓↓↓↓

 

     注意:大家尽量把cacheEctent的值调小,缓存越小,后面获取position越快

List<int> positions;
int memoryPosition;

child: new ListView.builder(
    cacheExtent: 30.0,
    itemBuilder: (context, index) {
       //根据positions==null来判断是否已经初始化
       if(positions!=null) {
            //已经初始化过的话 滑动自行调整头和尾
            if (index > positions.last) {
                positions.removeAt(0);
                positions.add(index);
            } else if (index < positions.first) {
                positions.removeLast();
                positions.insert(0, index);
            }
        }else{
            //记录初始化了多少个Item
            memoryPosition=index;
        }
},),

  NotificationListener的ScroStartNotification里面将变量memoryPosition 缓存成数组 positions,看代码

new NotificationListener(
    onNotification: (notification){
        if(notification is ScrollStartNotification){
            if(positions==null) {
                positions=new List();
                for(int i=0 ;i<=memoryPosition;i++){
                    positions.add(i);
                }
            }
        }
    },
    child: new ListView.builder(),

这样我们就知道头和尾了,并实时更新,可是头不一定是第一个可见Item,有可能是缓存的item,那我们怎么办呢,

我当时在想 我在调用时 如果我让 ListView在屏幕的位置 和  Item在屏幕中的位置+Item的高度  做比较  如果Item可见 ,那么Item在屏幕中的位置+Item的高度肯定大于ListView在屏幕的位置 (大家别忘了给ListView和它的Item们设置Key哟),好了,我们看代码

int firstChildPosition=positions.first;
int lastChildPosition=positions.last;

double chileGlobalPositionY;
double chileHeight;

//获取ListView在屏幕中的位置
double listViewGlobalPositionY=listViewKey.currentContext.findRenderObject().getTransformTo(null).getTranslation().y;
for(int i=firstChildPosition;i<=lastChildPosition;i++){
    if(adsorptionDatas[i].adsorptionKey.currentContext==null){
        continue;
    }
     //子控件在屏幕中的位置 用于计算第一个可见Item的位置
    chileGlobalPositionY=adsorptionDatas[i].adsorptionKey.currentContext.findRenderObject().getTransformTo(null).getTranslation().y;
    //控件高度 用于计算第一个可见Item的位置
    chileHeight=adsorptionDatas[i].adsorptionKey.currentContext.findRenderObject().paintBounds.size.height;
    //如果在屏幕中可见
    if(chileGlobalPositionY+chileHeight>listViewGlobalPositionY){
        //TODO i就是第一个可见Item的位置
        break;
    }
}

这样,我们的第一个可见Item的位置就获取到了

详细代码可参考https://github.com/baoolong/PullToRefresh 吸顶布局模块的代码

Flutter 中,ListView 提供了一种灵活的方式来实现上下滚动。要实现列表整行滚动,你可以使用 `SliverList` 组件配合 `ScrollPhysics` 和适当的 `CrossAxisScrollbar`。这允许你在滑动时保持整个列表项而不是单个项目移动。以下是一个简单的示例: ```dart // 使用 SliverList 替代 ListView return SliverList( delegate: SliverChildBuilderDelegate( (context, index) { return ListTile( title: Text('Item ${index + 1}'), // 其他列表项内容... ); }, builderExtent: const Size.fromHeight(kListRowHeight), // 假设 kListRowHeight 是每个列表项的高度 ), physics: BouncingScrollPhysics(), // 或者根据需要选择其他滚动效果 ); // 如果你想在屏幕边缘添加滚动条,可以这样配置: return SliverList( ... scrollDirection: Axis.vertical, shrinkWrap: true, // 让列表只扩展到内容大小 scrollbar: AlwaysScrollableScrollController(), ); ``` 当用户滚动时,整个列表会一起移动,从而达到整行滚动的效果。如果你需要在软键盘弹出时自动滚动到最后一项,可以监听键盘事件并相应地调整 `ListView` 的位置,就像第二个引用中的解决方案那样: ```dart FocusNode focusNode = FocusNode(); TextEditingController textEditingController = TextEditingController(); // 在构建 ListView 之前设置焦点节点 TextField( controller: textEditingController, focusNode: focusNode, // 添加其他属性... ) // 监听键盘事件 focusNode.addListener(() { if (textEditingController.has焦点 && !KeyboardManager.isKeyboardShowing()) { // 当失去焦点且键盘隐藏时,滚动到底部 setState(() { List<Widget> items = _items.toList(); // 获取当前列表项 final lastItemIndex = items.length - 1; double offset = lastItemIndex * kListRowHeight; // 假设高度与之前一致 // 设置 ListView 的 ScrollPosition 的 offset Scrollable.of(context).position.animateTo( ScrollPosition.withOffset(offset), duration: Duration(milliseconds: 200), ); }); } }); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

baoolong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值