实现延时加载的listview

作者: 人在广州_2017 | 来源:发表于2017-07-07 19:51 被阅读68次

因为项目需要,物品背包最多可能展示两百个物品,所以萌生了建立一个延时加载的listview的想法。

所谓的延时加载( lazily load ),即只需要展示当前所能看到的页面内容,其余的内容按需要再进行延时加载。这样,在加载200个item的场合,第一次实际才加载一屏的数据,在移动设备上,这并不会感到明显的卡顿。

动手之前,列举了一下需要封装的目标:

  1. 设置缩进、列距、行距、列数相关的属性
  2. 在控件内部监听当前位置,是否触发再加载

传统的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)

相关文章

网友评论

    本文标题:实现延时加载的listview

    本文链接:https://www.haomeiwen.com/subject/dlyrhxtx.html