因为项目需要,物品背包最多可能展示两百个物品,所以萌生了建立一个延时加载的listview的想法。
所谓的延时加载( lazily load ),即只需要展示当前所能看到的页面内容,其余的内容按需要再进行延时加载。这样,在加载200个item的场合,第一次实际才加载一屏的数据,在移动设备上,这并不会感到明显的卡顿。
动手之前,列举了一下需要封装的目标:
1. 设置缩进、列距、行距、列数相关的属性
2. 在控件内部监听当前位置,是否触发再加载
传统的listview api会是这个样子的,每次主动push都会create一个item加到界面显示:
1 | for i,item in enumerate (items): |
2 | list_view.push_item( item ) |
而由于在延时加载item的时机是在内部触发的,用户并不知道什么时候item被创建,因此新的api必须传递一个 on_item_callback 到控件内部,由内部来控制调用,因此有以下调用:
1 | for i,item in enumerate (items): |
2 | list_view.push_item_data( item ) |
3 | list_view.load_item( len (items), on_item_callback ) |
需求确定以后,开始动手编码,控件的属性有以下:
04 | self ._on_item_callback = None |
10 | self ._total_height = 0 |
核心的加载函数 _load_item:
01 | def _load_items( self ): |
02 | lv_size = self ._list_view.getContentSize() |
03 | self ._list_view.setItemsMargin( self .row_spacsing) |
06 | rest = self ._total - self ._loaded |
07 | while rest > added and added < self .load_step: |
08 | item = self ._on_item_callback( self ._items[ self ._loaded]) |
09 | item_size = item.getContentSize() |
11 | cur_col = self ._loaded % self ._cols |
13 | llist = ccui.Widget.create() |
14 | llist.setContentSize(cc.Size(lv_size.width, item_size.height)) |
15 | self ._list_view.pushBackCustomItem(llist); |
16 | self ._lists.append(llist) |
17 | self ._total_height + = item_size.height + self .row_spacsing |
19 | llist = self ._lists[ len ( self ._lists) - 1 ] |
21 | x = self .col_padding + (item_size.width + self .col_spacsing) * cur_col |
22 | item.setPosition(cc.Vec2(x, 0 )) |
24 | self ._items[ self ._loaded][ 'item' ] = item |
需要加载数据检查 need_load:
3 | if self .get_total_num() > self .get_loaded_num(): |
6 | if self .need_more_data_callback: |
7 | self .need_more_data_callback() |
need_load的调用,是建立一个触摸listenner,检查上拉滚动的位置来触发。
另外,self._items 用list存放 { 'index', 'data', 'item' },便于listview从中间 insert和pop item进行重新排版。
触发列表加载更多的列表项有多种方法:
① 滚动到底部时,再进行一次上拉操作,触发再加载,这种类似于微信上拉刷新朋友圈的方法,适合显式的网络请求场景
② 滚动快到底部,用户还没浏览到最后的时,内部偷偷地触发一次再加载,保持浏览的持续性
这里需要注意的是,self.load_step 一次加载的个数不宜设太大,设置到用户刚好感受不到卡顿感就可以,一般的手机一次加载一个手机屏幕高的列表项是不会卡顿的
下面是内部刷新的实现:
01 | def _init_list_view( self ): |
02 | self ._list_view.setBounceEnabled( True ) |
05 | self ._clear_listener() |
06 | self ._event_listener = cc.EventListenerTouchOneByOne.create() |
07 | self ._event_listener.setSwallowTouches( False ) |
09 | def on_touch_began(touch, event): |
10 | pos = touch.getLocation() |
11 | pos = self ._list_view.convertToNodeSpaceAR(pos) |
12 | rect = self ._list_view.getBoundingBox() |
13 | rect = cc.Rect( 0 , 0 , rect.width, rect.height) |
15 | if rect.containsPoint(pos): |
20 | def on_touch_moved(touch, event): |
23 | def on_touch_ended(touch, event): |
24 | iner_y = self ._list_view.getInnerContainer().getPosition().y |
25 | size = self ._list_view.getInnerContainerSize() |
30 | self ._event_listener.setOnTouchBeganCallback(on_touch_began) |
31 | self ._event_listener.setOnTouchMovedCallback(on_touch_moved) |
32 | self ._event_listener.setOnTouchEndedCallback(on_touch_ended) |
33 | self ._list_view.getEventDispatcher().addEventListenerWithFixedPriority( self ._event_listener, - 100 ) |