Cocos2d-x 3.0final 终结者系列教程21ScrollView原理与使用

时间:2022-12-04 19:05:25

CCScrollView的滚动是藉助于其内部容器的位置变动来达到的,再加以遮盖/剪切便实现不可见的部分进行隐藏。

藉助于CCScrollView,我们可以实现分页效果,简单的富文本,下拉式按钮等。

创建一个CCScrollView式的滚动视图,首先要创建一个容器,此容器可以必须是Node或其子孙类。如下: 

1
2
3
4
5
6
7
8
9
10
     self.layerContainer = display.newColorLayer(ccc4(10, 20, 30, 10))
     self.layerContainer:setTouchEnabled( true )
     self.layerContainer:setPosition(ccp(1, 0))
     self.layerContainer:setTouchSwallowEnabled( false )
     self.layerContainer:addNodeEventListener(cc.NODE_TOUCH_EVENT, function(event)
         return  self:onCellCallback(event.name, event.x, event.y)
     end)
self.widgetContainer = display.newSprite()   
     :align(display.LEFT_BOTTOM, 0, 0)
     :addTo(self.layerContainer)


我们便创建了一个容器,此容器是一个带有颜色的Layer,并设置了位置以及触摸相关事件,还为其添加了一个孩子节点,并使触摸事件绑定到了onCellCallback方法,此方法并没有做过多的处理,只是简单的记录容器触摸事件的起始和结果,如下:

1
2
3
4
5
6
7
8
function LLScrollView:onCellCallback(event, x, y)
     if  event ==  "began"  then
         self.bolTouchEnd =  false
         return  true
     elseif event ==  "ended"  then
         self.bolTouchEnd =  true
     end
end


这个方法可有可无,但为了实现下面的分页就必不可少了,通过self.bolTouchEnd的标记,在后面的代码中才能知道该何时处理,以及更好对CCScrollView进行滚动操作。下面我们来创建一个CCScrollView,并把刚才创建的Layer添加为其的容器:

1
2
3
4
5
6
7
8
9
10
     self.scrollView = CCScrollView:create()
     <span style= "white-space:pre" >   </span>self.scrollView:setContentSize(CCSizeMake(0, 0)) -- 設置內容大小
     <span style= "white-space:pre" >   </span>self.scrollView:setViewSize(CCSizeMake(self.scrollWidth, self.scrollHeight)) -- 設置可見大小
     <span style= "white-space:pre" >   </span>self.scrollView:setPosition(ccp(100, 100)) -- 設置位置
     <span style= "white-space:pre" >   </span>self.scrollView:setContainer(self.layerContainer) -- 設置容器
     <span style= "white-space:pre" >   </span>self.scrollView:setDirection(kCCScrollViewDirectionVertical) -- 設置滾動方向
     self.scrollView:setClippingToBounds( true ) -- 設置剪切
     self.scrollView:setBounceable( true )  -- 設置彈性效果
     self.scrollView:setDelegate( this ) -- 註冊為自身
     self:addChild(self.scrollView)


通过setContentSize制定CCScrollView的大小,当然这是可有可无的,其实并不是指定CCScrollView的大小,而是指定其容器的大小,一般我们都会在给容器添加数据的时候,再调整容器的大小;设置视图的可见范围就必不可少了,setViewSize方法为我们提供了设置视图大小,设置视图大小便指定了可见范围,setClippingToBounds方法设置剪切,如果不设置,可见范围的设置便成了虚设,通过setBounceable方法指定视图滚动过程中是否能够滚动,为了让CCScrollView显得不那么僵硬,一般会设置为true;同时我们要设置滚动的方向setDirection,有3个方向,水平、竖直、双向,setDelegate(this)指定注册为自身。通过以上代码我们便创建了一个完整的CCScrollView。


为了实现滚动,还要为其添加滚动监听事件,也就是写个方法把其绑定到CCScrollView的滚动事件上,例如:

1
self.scrollView:registerScriptHandler(scrollView2DidScroll, CCScrollView.kScrollViewScroll)

这一行代码就把方法scrollView2DidScroll绑定到了CCScrollView.kScrollViewScroll事件上,要做什么处理工作便在scrollView2DidScroll方法中添加,比如我们要实现分页,或滚动时实现一个item的滚动,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
local function scrollView2DidScroll()
     if  self.bolTouchEnd ==  true  then
         self.bolTouchEnd =  false
         local offy = self.layerContainer:getPositionY()
         local miny = self.scrollHeight-self.cellNums*self.cellHeight
         if  offy < 0 and offy > miny then
             local item = -(math. abs (offy)%self.cellHeight)
             if  item <= -self.cellHeight/2 then
                 if  offy < self.preOffy then
                     item = offy-item-self.cellHeight 
                 else
                     item = offy-item-self.cellHeight
                 end
             else
                 item = offy-item
             end
             self.scrollView:setContentOffset(ccp(1, item),  true )
         end
     end
end


顺便提一下,一些全局变量的定义在这里:

1
2
3
4
5
self.scrollTop = 208
self.scrollHeight = 208
self.scrollWidth = 152
self.cellHeight = 52
self.cellNums = 2

至于什么意思,看到命名字段应该还是浅显易懂的,就不一一解释了,其中self.preOffy是记录上一次CCScrollView的偏移量。分页也很简单,首先假定我们的ScrollView的偏移量都是负值,确实也是负值。由于CCScrollView会自动纠正第一项和最后一项的位置,所以在第一项和最后一项时我们不做处理,让CCScrollView的内部方法去实现。如果CCScrollView的偏移量对一个item取余数,如果余数大于item的一半我们就分页,否则就归位。代码if offy < self.preOffy then是为了判定滚动方向是上还是下(我的这个CCScrollView的设置方向是竖直的),好了到此为止我们的完整的带分页的CCScrollView就实现了。


现在要对其添加数据,方便起见我又创建了一个类名称为LLScrollItem,实现一个item的类,并为其添加了一个ON_TOUCH_EVENT的事件,完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
local LLScrollItem =  class ( "LLScrollItem" , function()
     return  display.newNode()
end)
LLScrollItem.ON_TOUCH_EVENT =  "on_touch_event"
function LLScrollItem:ctor(params)
     cc(self):addComponent( "components.behavior.EventProtocol" ):exportMethods()
     self.text = params.text
     local label = ui.newTTFLabel({
         text = params.text,
         font =  "Arial" ,
         size = 20,
         })
     label:setTouchEnabled( true )
     label:addNodeEventListener(cc.NODE_TOUCH_EVENT, function(event)
         self:onTouch(event)
     end)
     local rect = {
         {0, 0},
         {150, 0},
         {150, 52},
         {0, 52},
         {0, 0}
     }
     local r, g, b, s = math.random(0, 255), math.random(0, 255), math.random(0, 255), math.random(0, 255)
     local layerColor = display.newColorLayer(ccc4(r, g, b, s)):addTo(self)
     :align(display.LEFT_BOTTOM, 0, 0)
     layerColor:setContentSize(CCSizeMake(150, 52))
     local polygon = display.newPolygon(rect, 1):addTo(layerColor)
     :align(display.LEFT_BOTTOM, 0, 0)
     polygon:setLineWidth(1)
     polygon:setLineColor(ccc4f(255, 0, 0, 255))
     label:addTo(polygon)
     label:align(display.CENTER, 75, 26)
     label:setColor(ccc3(255-r, 255-g, 255-b))
end
function LLScrollItem:onTouch(event)
     self:dispatchEvent({name=LLScrollItem.ON_TOUCH_EVENT, text = self.text})
end
 
return  LLScrollItem


每一个item是一个有范围界定有色层,并用一个Box包裹,并添加了一个独一的文本,用来以后区别我们点击了哪个item。


利用上面的item类我们就可以为CCScrollView添加数据了:

1
2
3
4
5
6
7
8
9
10
for  i=1, self.cellNums  do
     self.scrollTop = self.scrollTop - self.cellHeight
     local cell = LLScrollItem. new ({text= "ScrollItem: "  .. i}):addTo(self.widgetContainer)
     :align(display.LEFT_BOTTOM, 0, self.scrollTop)
 
     cc.EventProxy. new (cell, cell)
         :addEventListener(cell.ON_TOUCH_EVENT, function(event)
             self.label:setString(event.text)
         end)
end

因为容器内容变化了,所以要改变其大小,以便能够完全承载所有数据;设置容器的位置是容器能够达到的最低点,容器的孩子self.widgetContainer,其实是一个真正的承载数据的节点,我们在这里要设置其能够达到的最高高度。


我们之所以要给容器添加一个子节点,并所有数据都添加到其子节点上,是为应对动态增删其数据内容的情况,并使CCScrollView的显示正常。


我们再创建两个控件,一个Label一个Button:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
     self.label = ui.newTTFLabel({
         text =  "Hello, World"
         size = 20,
         color = ccc3(163, 140, 14),
         align = ui.TEXT_ALIGN_CENTER
         })
     :pos(display.cx+100, display.cy+100)
     :addTo(self)
     self.button = cc.ui.UIPushButton. new ({
         disabled =  "nil" ,
         normal =  "res/GreenScale9Block.png" ,
         pressed =  "res/PinkScale9Block.png" ,
         })
     :align(display.CENTER, display.cx+100, display.cy-100)
     :addTo(self)
     :onButtonClicked(
         function()
             self:addItem()
         end
         )


这个Label就是上面所说的self.label,此Button是为了实现动态添加数据的功能,点击Button就为CCScrollView添加数据,我们把其相应事件绑定到了addItem方法,此方法实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function LLScrollView:addItem()
     self.scrollTop = self.scrollTop - self.cellHeight
     self.cellNums = self.cellNums+1
     local cell = LLScrollItem. new ({text= "ScrollItem: "  .. self.cellNums}):addTo(self.widgetContainer)
     :align(display.LEFT_BOTTOM, 0, self.scrollTop)
 
     cc.EventProxy. new (cell, cell)
         :addEventListener(cell.ON_TOUCH_EVENT, function(event)
             self.label:setString(event.text)
         end)
 
     local h = self.cellNums*self.cellHeight
     if  h < self.scrollHeight then
         h = self.scrollHeight
     end
     self.layerContainer:setContentSize(CCSizeMake(self.scrollWidth, h))
     self.widgetContainer:setPositionY(h-self.scrollHeight)
     self.layerContainer:setPositionY(self.scrollHeight-h)
     self.preOffy = self.layerContainer:getPositionY()
     local h1 = self.scrollHeight-h
     if  h1 < 0 then h1 = 0 end
     self.layerContainer:setPositionY(h1)
end


同样的,在添加完数据之后,一定要调整容器及其子节点的位置及大小,否则显示出问题。到此位置我们已经实现了一个完整的能够实现分页、动态添加数据的CCScrollView,至于动态删除,仿照动态添加自然也就出来了。