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