高效可变行高列表行定位算法

图1. universe

背景

最近因为项目需要,用到了DirectUI开源库SOUI,在使用listview控件时,觉得该控件挺好用,效率也不错(在此感谢启程软件,为作者点个赞),就分析了一下其实现原理, 方便以后查阅。

对于列表控件,行高一般都是固定的,但在实际项目中,的确会有可变行高的情况。传统列表控件很难满足这个需求,SOUI却可以很容易实现该需求。

算法原理

在重绘列表控件时,只需要绘制可视区域就行了。但在绘制项的时候,需要在控件内部维护列表项的位置(postion)和列表项的索引(index)关系。对于固定行高的列表来讲比较简单:position = index * height
但对于可变高列表项来说,就不好办了,一个简单的办法就是每次都遍历一下所有项,直到找到一个项使得position处于该项内,可知这种方法的时间复杂度是O(n),这种方法对于数据量比较少的列表来讲是可行的,但数据量比较大时,就会出现性能瓶颈。
SOUI对listview列表控件采用不断分组的方式构建出一棵索引树,查找和修改的时间复杂度都是O(log(n)),举个例子:

现在有14个列表项,每个项的高度分别为[1,2,3,1,2,3,1,2,3,1,2,3,4,5],
如果按照3个列表项为一组构成一个结点(该结点的值为3个列表项的高度和)。

第一次分组:[6,6,6,6,9]
第二次分组:[18,15]
第三次分组:[33]
最后得到的索引树为(虚线部分只是为了说明,不在索引树中):

图2. 索引树

构建出上述索引树,在此索引树中会进行四个最重要操作:

  • 通过位置计算列表项索引Position2Item
    给定一个位置,计算出该位置所在列表项的索引,计算方法:
1.从根节点开始查找,对非叶子结点运用2,3策略。
2.如果结点高度大于等于给定位置,查找其子结点。
3.如果结点高度小于给定位置,给定位置减去结点高度并继续查找右边兄弟结点。
4.在找到的叶子结点中,遍历分组列表项,直到找到满足条件的索引。

例如给定位置position=22, 要计算出该位置所在列表项的索引,如下图:

图3. Position2Item

其计算步骤为:

1. 从根结点开始,22小于33,运用策略2,查找其子结点。
2. 22大于18,运用策略3,查找右边兄弟结点,并减去已查找到的高度22 - 18 = 4,
   根据分治法,也就是我们只需要在右边兄弟结点中再次按照相同的策略查找符合条件的叶子结点。
3. 4小于15,运用策略2,查找其子结点。
4. 红色结点6为叶子结点,符合条件。
5. 遍历图中红色结点的分组,找到第三个项(高度为3)满足条件,由于前面分了3组,
   计算出position=22所在列表项的index为11。
  • 通过列表项索引计算位置Item2Position
图4. Item2Position

给定一个索引计算其位置,是上面的逆过程,但相对来说要简单些,计算方法是:

通过索引,可以计算出列表数项所在的分组及在分组中的子索引:
分组索引 = 列表项索引 / 分组大小
分组子索引 = 列表项索引 % 分组大小
由分组索引可以定位到分组叶子结点,可以计算数该分组叶子结点的位置,算法如下:
    1.累加该叶子结点左兄弟结点高度
    2.如果该叶子结点有父结点,对其父结点运用策略1
    3.运用1,2策略所得的结点高度和即为该分组叶子结点的位置
计算一个分组叶子结点的位置也可理解成累加其所有左边的非直系结点的高度和, 如图4绿色部分。
分组叶子结点的位置再加上分组内索引的位置即可求得列表项索引的位置了。

例如,要计算列表项索引index=11的项的位置:

1. 分组索引 = 11 / 3 = 3
2. 分组子索引 = 11 % 3 = 2
3. 第三个分组叶子结点为上图红色结点,应用上述算法可得该结点的高度为18,
   然后再累加分组内子索引的高度为1 + 2 = 3, 可计算出索引为11列表项位置为18 + 3 = 21.
  • 设置列表项的高度SetItemHeight
通过索引,可以计算出列表数项所在的分组及在分组中的子索引:
分组索引 = 列表项索引 / 分组大小
分组子索引 = 列表项索引 % 分组大小
通过分组索引和分组内子索引可以设置一个项的高度,叶子结点高度的改变需要更新其直系父结点的高度,
时间复杂度同样是O(log(n))
  • 获取列表项的高度GetItemHeight
通过索引,可以计算出列表数项所在的分组及在分组中的子索引:
分组索引 = 列表项索引 / 分组大小
分组子索引 = 列表项索引 % 分组大小
由分组索引和分组内子索引可以获得列表项的高。

数据结构

通过上面分析,需要用一个树型结构维护分组;另外,为了能够通过列表项索引定位到叶子结点,还需要一个分组的列表存储分组索引到叶子结点的映射。如下:

图5. 数据结构

更多参考

[1]. https://home.cnblogs.com/u/setoutsoft/

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

推荐阅读更多精彩内容