Flutter-ListView重用机制分析和实现jumpTo(index)功能

Flutter官方SDK目前还没有支持ListView.jumpTo(index)的功能,但是这个功能是很多App都需要的.想要实现这个功能需要先要了解ListView的item"重用"机制.

重用机制

先介绍一下iOS的cell重用机制,然后对比ListView的item"重用"机制.

iOS的TableViewCell重用机制

  • 通过对每一个类型的cell绑定重用id标志
  • 根据重用id去取出重用池里面的cell对象,池子里没有或者数量不够,tableView会new一个新的出来.
  • 去更新该cell,调整frame并移动到可视区域.
/// 注册cell
- (void)registerClass:(nullable Class)cellClass forCellReuseIdentifier:(NSString *)identifier;

/// 取出cell
- (__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath;

/// 更新cell
cell.data = data;

ListView的item"重用"机制

ListView因为没有item的重用id,所以每次滑动ListView,它会重新创建、布局、绘制可见区域内的item,一般会多绘制可见区域以外2-4个item,即预加载机制,这点跟iOS有点类似.当item不在屏幕显示的时候,会执行dispose.
Flutter整个框架对UI进行了优化,所以不必担心重复创建item的内存消耗问题.ListView的重用机制就是Flutter对UI的重用机制,优化更加彻底,会重用item对应的element和renderObject对象,因为item对象每次都会重新创建.item对象是轻量级的,它关联的renderObject和element才是正在消耗内存的,只要这两个有缓存机制就没什么大问题.而且ListView必须滚动到指定位置之后才会触发相关区域item的创建、布局等操作.

实现jumpTo(index)功能

ScrollController提供jumpTo(double value)方法,所以我们只要知道index对应的offset即可,对于item高度一样的ListView,比较简单.

等高的item

var offset = itemIndex * itemHeight;
scrollController.jumpTo(offset);

非等高的item

难点在于item高度不一样的时候,有几种可用方案:

提供item的高度的回调方法
这样的方式其实跟iOS类似.iOS的方法:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
flutter需要实现double itemHeight(int index)方法,这样就可以计算offset.

double itemHeight(int index){
    return 不同高度;    
  }
  
  double offsetOnIndex(int index) {
    double offset = 0.0;
    for(int i = 0; i < index; i ++){
      offset += itemHeight(i);
    }
    return offset;
  }
    _scrollController.jumpTo(offsetOnIndex(index));

itemHeight方法实现起来比较麻烦,你需要给item增加height一个计算方法,尤其是碰到复杂的item.写过iOS的都知道,这玩意不好写,但iOS至少有一个自动计算cell高度的三方框架.而且flutter中的高度计算更加不好写,因为flutter的布局体系更复杂,实现难度更大.

创建一个SingleChildScrollView,并把ListView的所有item同样创建一份给SingleChildScrollView

利用SingleChildScrollView全部加载child的机制,可以很方便的计算出所有的item的height,然后就可以累加之前的所有item并计算出任意item的offset,但是缺点是如果item很多,会消耗大量的内存,而且SingleChildScrollView自身必须要显示出来才会layout它的child.

利用ListView的预加载机制,逐步加载未显示的item

需要自定义item,使用SizeChangedLayoutNotifier,它可以监听到item布局完成的通知,但是需要自定义修改一下它的实现,因为它的第一次布局完成不会发通知.每次布局完成把布局结果放入一个Map<int,double>缓存起来.当你需要滚动到某一个index的时候,取出<index的所有item缓存的高度累加即可.但是,并没有想象的那么简单.如果是你jumpTo到已经显示过的item,这样是可以,因为显示过的item已经有高度缓存了.没有显示过的是没有缓存过高度的.这里就出现了一个矛盾.

  • 想滚到到指定index,前提是index之前的item都必须已经布局完成
  • index之前的item都已经布局完成,才能缓存他们的高度,然后才能滚到指定的index.

所以存在滚动<=>布局相互等待问题了.那该如何解决?

可以利用ListView的预加载机制来做,每次我们可以使offset+=1,触发预加载,等待预加载出来的item布局完成之后直接滚动到最后的item的offset,一直循环这个逻辑就可以滚动到目标index,但要注意判断边界条件.
伪代码:

var tryOffset = 1;
var totalOffset = 0;
var startIndex = 0;
while(true) {
  scrollController.offset += tryOffset;
  // 边界条件判断
  // 1.超出所有item数量了
  // 2.滚动到底了
  // 3.到达目标index了
  // 等待scrollController.position.moveTo完成
  // 等待新的item布局完成
  totalOffset += 新item的高度(从缓存取);
  startIndex ++;
}

// 结束:
scrollController.position.moveTo(totalOffset);

最后

我写的flutter库list_view_item_builder的解决方案就是利用预加载来实现ListView.jumpTo(index)的.目前也没有发现什么问题,不管是还未布局过的,还是布局过的item都是可以正常滚动到指定index.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271