您的位置:首页 > 其它

实现聊天窗口动态可视区加载的listview

2016-03-31 12:48 274 查看

定义

可视区域加载,顾名思义,即listview只加载数据源中的可视部分,而不可视部分则不需要加载。其实质也是懒加载技术(简称lazyload)的一种,已经不是什么新鲜的技术了,js程序员对网页性能优化经常会采用这种方案,大型网站中都有lazyload的身影,如谷歌的图片搜索页,淘宝网等。lazyload的核心是按需加载。

使用场合

在游戏中,还有哪些地方需要用到这种可视区域的listview呢?没错,聊天窗口。现在手游的聊天内容展示越来越丰富,再不是一两年前的手游纯文字聊天能比拟的。玩家头像、富文本、超链接、动态表情,在加上数量庞大的聊天记录,不做任何优化的话,仅打开一个聊天窗口一段时间,也会消耗不少的cpu和内存。如果项目有这种聊天需求,也是时候考虑优化方案了。

方案设计

在设计优化方案之前,参考了下梦幻手游的世界聊天,看看这个同样用cocos2dx-lua的scrollview控件,添加了怎样的特性。偶然发现,把加载了一定数量的消息滚动到中央,手指在聊天框中间做上下幅度4cm的拖动,无论是向上还是向下滚动,都会发生一瞬间的卡顿。到这里其实已经印证了我猜想中的方案了,下面来说一下这个方案。

在我的方案中,数据源(青色背景),加载区域(蓝色),可视区域(灰色)可以用下面的图来描述(有点丑,将就下)





有点像滑动的窗口故取名SlideWindow。

当屏幕可视区域滚动到已加载区域的边缘时候,加载区域往滚动方向加载更多数据,并且加载区域为保持一定数量的item,删除掉对应反方向的数据。这种可视区域维持一定数量的item,应该就是导致梦幻聊天在某个临界区上下滚动一瞬间卡顿的原因了。使用这种方案,无论梦幻的聊天累积了多少条聊天记录,每次都只是加载一定数量的item,在拖动时按需加载,调和了数量大和首次打开界面流畅的矛盾。梦幻的聊天还有一个非常人性的设计,当可视区域在顶部的时候,设置为锁定状态(新消息置顶),而当可视区在中间时候,为非锁定状态(新消息显示数目提示,而不会让玩家浏览历史记录时候受到新消息置顶的困扰)。

实现

这里我用的cocos2dx-lua实现了一个原型,暂时还没有加入到项目中,实现思想跟之前实现的lazylistview差不多。

这是封装的成员属性,维护已加载区域的头指针和尾指针。

01
function
SlideWindow:ctor(list_view)
02
self.list_view_
= list_view
03
04
self.max_size_
=
40
--
列表项的最大个数
05
self.load_step_
=
20
--
每次滑动加载的步长
06
self.head_
=
1
--
当前头的index
07
self.tail_
=
0
--
当前尾的index
08
09
self.on_item_callback_
= nil
10
end
当检测到需要加载数据时,调用need_load

01
-
-
触发加载,
-
1
为向上加载,
1
为向下加载
02
-
-
@
return
返回本次加载的个数
03
function
SlideWindow:need_load(direction)
04
local
ret
=
0
05
local
start
=
self
.tail_
+
1
06
if
direction
<
0
then
07
start
=
self
.head_
-
1
08
end
09
 
10
-
-
向上没有数据,返回
11
if
start
<
=
0
then
12
return
0
13
end
14
15
-
-
尝试加载load_step个列表
16
local
item_tpl
=
1
17
while
item_tpl
and
ret
<
self
.load_step_
do
18
item_tpl
=
self
.on_item_callback_(start
+
ret
*
direction)
19
if
item_tpl
then
20
if
direction
<
0
then
21
if
self
.head_
=
=
1
then
22
break
23
end
24
self
.head_
=
self
.head_
-
1
25
self
.list_view_:insertCustomItem(item_tpl,
0
)
26
else
27
self
.tail_
=
self
.tail_
+
1
28
self
.list_view_:pushBackCustomItem(item_tpl)
29
end
30
ret
=
ret
+
1
31
end
32
end
33
34
if
ret
=
=
0
then
35
return
ret
36
end
37
38
-
-
判断是否达到最大数量
39
if
self
.tail_
-
self
.head_
+
1
>
self
.max_size_
then
40
for
i
=
1
,
self
.tail_
-
self
.head_
-
self
.max_size_
do
41
if
direction
<
0
then
42
self
.tail_
=
self
.tail_
-
1
43
self
.list_view_:removeLastItem()
44
else
45
self
.head_
=
self
.head_
+
1
46
self
.list_view_:removeItem(
0
)
47
end
48
end
49
end
50
51
return
ret
52
end
监听函数,控制触发在加载的时机

01
-
-
监听SlideWindow滚动,动态加载列表项
02
function
SlideWindow:_init_listener()
03
local
extend
=
false
04
local
last_pos
=
0
05
local
distance
=
60
06
local
inner
=
self
.list_view_:getInnerContainer()
07
local
outer_size
=
self
.list_view_:getContentSize()
08
function
scroll_callback(sender, eventType)
09
if
eventType
=
=
SCROLLVIEW_EVENT_SCROLLING
then
10
-
-
print
(
"正在滚动"
)
11
local
inner_size
=
self
.list_view_:getInnerContainerSize()
12
local
pos
=
inner:getPositionY()
13
14
if
not
extend
then
15
if
pos
>
-
distance
and
last_pos
-
pos
<
0
then   
-
-
接近底部
16
extend
=
true
17
self
:need_load(
1
)
18
elseif
inner_size.height
-
(outer_size.height
-
pos)
< distance
and
last_pos
-
pos
>
0
then
-
-
接近顶部
19
extend
=
true
20
self
:need_load(
-
1
)
21
end
22
extend
=
false
23
end
24
last_pos
=
pos
25
end
26
end
27
self
.list_view_:addEventListenerScrollView(scroll_callback)
28
end
到这里已经基本写完了,大概一百多行代码,还有更多流畅性的问题就要留待使用中逐步优化了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: