Flutter 获取ListView的第一个可见的Item

Flutter 获取ListView的第一个可见的Item


Flutter的ListView并没有直接提供方法和属性来告知我们当前列表的第一个Item的index,因此查看ListView的源码,可以发现其Item的创建与SliverChildDelegate相关。这里拿ListView.custom举例。

const ListView.custom({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    this.itemExtent,
    @required this.childrenDelegate,
    double cacheExtent,
    int semanticChildCount,
  }) : assert(childrenDelegate != null),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount,
       );

其中有个参数是childrenDelegate,该参数的定义如下。

/// A delegate that provides the children for the [ListView].
///
/// The [ListView.custom] constructor lets you specify this delegate
/// explicitly. The [ListView] and [ListView.builder] constructors create a
/// [childrenDelegate] that wraps the given [List] and [IndexedWidgetBuilder],
/// respectively.
final SliverChildDelegate childrenDelegate;

可知,列表的Item的创建工作是由该类完成的。继续深入,发现SliverChildDelegate中存在一个方法,

/// Called at the end of layout to indicate that layout is now complete.
///
/// The `firstIndex` argument is the index of the first child that was
/// included in the current layout. The `lastIndex` argument is the index of
/// the last child that was included in the current layout.
///
/// Useful for subclasses that which to track which children are included in
/// the underlying render tree.
void didFinishLayout(int firstIndex, int lastIndex) { }

震惊!!!这不就是我们所需要的第一个可见的Item吗!!!于是自定义MyDelegate,继承SliverChildDelegate并重写didFinishLayout方法。

class MyDelegate extends SliverChildDelegate {

  final int childCount;
  final IndexedWidgetBuilder builder;

  MyDelegate({this.childCount, this.builder});

  @override
  Widget build(BuildContext context, int index) {
    return builder(context, index);
  }

  @override
  bool shouldRebuild(SliverChildDelegate oldDelegate) => true;

  @override
  int get estimatedChildCount => childCount;

  @override
  void didFinishLayout(int firstIndex, int lastIndex) {
    print('第一个Item是$firstIndex,最后一个Item是$lastIndex');
  }
}

但是程序运行起来,发现Item不能保存状态了,所以不能在build方法中直接返回builder(context, index),必须得重写该方法。这时候发现,flutter已经帮我们做好了这个工作,就是SliverChildBuilderDelegate,直接把里面的build方法以及相关的变量复制过来就好了。

@override
Widget build(BuildContext context, int index) {
   assert(builder != null);
   if (index < 0 || (childCount != null && index >= childCount))
     return null;
   Widget child;
   try {
     child = builder(context, index);
   } catch (exception, stackTrace) {
     child = _createErrorWidget(exception, stackTrace);
   }
   if (child == null)
     return null;
   final Key key = child.key != null ? _SaltedValueKey(child.key) : null;
   if (addRepaintBoundaries)
     child = RepaintBoundary(child: child);
   if (addSemanticIndexes) {
     final int semanticIndex = semanticIndexCallback(child, index);
     if (semanticIndex != null)
       child = IndexedSemantics(index: semanticIndex + semanticIndexOffset, child: child);
   }
   if (addAutomaticKeepAlives)
     child = AutomaticKeepAlive(child: child);
   return KeyedSubtree(child: child, key: key);
 }

搞定!!!
等等。。。当我某一个Item进行状态保存以后,firstIndex的值就一直是该Item的index,并且lastIndex也指向列表中最后一个保存状态的Item。所以这个firstIndex应该是指向列表维护的Item中的第一个,跟我们想要的第一个可见的Item完全不是一回事嘛。这个时候,我在SliverChildDelegate中发现了另外一个方法。

/// Returns an estimate of the max scroll extent for all the children.
///
/// Subclasses should override this function if they have additional
/// information about their max scroll extent.
///
/// The default implementation returns null, which causes the caller to
/// extrapolate the max scroll offset from the given parameters.
double estimateMaxScrollOffset(
  int firstIndex,
  int lastIndex,
  double leadingScrollOffset,
  double trailingScrollOffset,
) => null;

嗯。。。。看起来也没啥特别的啊,注释里都没解释参数的意义,而且从名字来看,这个firstIndex跟didFinishLayout里的firstIndex应该是同一个东西吧。不死心的我在MyDelegate中又重写了这个方法并输出了firstIndex的值,结果证明,这就是我们想要的东西!!!同时lastIndex指向当前列表可见的最后一个Item。终于找到你,还好我没放弃~~(不过flutter也太坑了吧,啥也不说明,鬼知道是干嘛用的),完结,撒花。

  • 0
    点赞
  • 1
    收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论 5

打赏作者

u014803467

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

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值