实现延时加载的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