群里不止一次看到有人问如何让quick的listview在滑动结束后,自动修正item的位置,让其显示完整的item,而不是显示一半。
举个例子,下面是一个飞船列表,列表可视范围总共恰好容纳5个飞船。我们在快速滑动之后,需要让其自动修正下位置,使其依然显示5个完整的飞船,而不是前后的飞船显示不全!
并且我们额外添加一个小功能,列表可视范围的两边添加箭头指示,当那一边存在未显示的飞船时,则显示箭头!

quick的listview本身是无法实现这样的功能的,也没有相关的接口,所以我们需要自己修改扩展listview!
我这里说下整体思路,然后直接附上代码,需要的朋友自己阅读便可明白!
1.箭头的显示
箭头分为前后,前面的箭头是否显示的判断依据是列表中的第一个飞船的最左侧坐标是否在显示区域内;而后面的箭头是否显示的判断依据是列表中的最后一个飞船的最右侧是否在显示区域内。这是针对水平列表而言的,对于垂直列表,那就是对应上下了!
2.自动修正位置
当滑动结束后,我们首先获取到当前显示区域内第一个可见的飞船,然后判断飞船的中心坐标的X值是在可视区域左边的那一边,如果在左边,则将列表滑动至第二个可见飞船,如果在右边,则将列表滑动至第一个可见的飞船。这样就能够保证拖动列表后,列表内始终保持显示完整的5个飞船。最终,我们需要重写listview的滑动接口,以及添加自动修正的方法。代码如下:
local c = cc
local UIScrollView = c.ui.UIScrollView
local ShipListView = class("ShipListView", c.ui.UIListView)
function ShipListView:ctor(params, root)
ShipListView.super.ctor(self, params)
self.root = root
-- init arrow
local viewRect = params.viewRect
self.fArrow = display.newSprite("UI/Arsenal/ui_common_indicator.png"):addTo(self.root)
self.fArrow:setFlippedX(true)
self.fArrow:setVisible(false)
self.fArrow:setPosition(viewRect.x - 10, viewRect.y + viewRect.height/2)
self.bArrow = display.newSprite("UI/Arsenal/ui_common_indicator.png"):addTo(self.root)
self.bArrow:setPosition(viewRect.x + viewRect.width + 10, viewRect.y + viewRect.height/2)
self.bArrow:setVisible(false)
end
function ShipListView:scrollToItem(item)
end
function ShipListView:autoFixScroll()
local item, pos = self:getFirstVisibleItem()
local bound = item:getBoundingBox()
local nodePoint = self.container:convertToWorldSpace(
c.p(bound.x + bound.width/2, bound.y))
local index
if c.rectContainsPoint(self.viewRect_, nodePoint) then
index = pos
else
index = pos + 1
end
local toItem = self.items_
bound = toItem:getBoundingBox()
self:scrollToPos(-bound.x + self.viewRect_.x, 0)
end
function ShipListView:getFirstVisibleItem()
for i=1,#self.items_ do
if self:isItemInViewRect(self.items_*) then
return self.items_*, i
end
end
end
function ShipListView:checkArrowStatus()
if #self.items_ == 0 then
return
end
local fNeed, bNeed = self:isNeedArrow()
if fNeed then
self.fArrow:setVisible(true)
else
self.fArrow:setVisible(false)
end
if bNeed then
self.bArrow:setVisible(true)
else
self.bArrow:setVisible(false)
end
end
function ShipListView:isNeedArrow()
local fItem = self.items_
local bound = fItem:getBoundingBox()
local nodePoint = self.container:convertToWorldSpace(
c.p(bound.x, bound.y))
local fNeed = not c.rectContainsPoint(self.viewRect_, nodePoint)
bItem = self.items_#self.items_]
bound = bItem:getBoundingBox()
nodePoint = self.container:convertToWorldSpace(
c.p(bound.x + bound.width, bound.y))
local bNeed = not c.rectContainsPoint(self.viewRect_, nodePoint)
return fNeed, bNeed
end
function ShipListView:scrollToPos(x, y)
local scrollLength = c.pGetLength(c.pSub(c.p(x, y), self.position_))
self.position_ = c.p(x, y)
local action = c.MoveTo:create(0.5, self.position_)
self.scrollNode:runAction(c.EaseElasticOut:create(action))
end
function ShipListView:getAllItem()
return self.items_
end
function ShipListView:getFirstItem()
return self.items_
end
function ShipListView:getLastItem()
return self.items_#self.items_]
end
-- override
function ShipListView:onTouch_(event)
if "began" == event.name and not self:isTouchInViewRect(event) then
printInfo("UIScrollView - touch didn't in viewRect")
return false
end
if "began" == event.name and self.touchOnContent then
local cascadeBound = self.scrollNode:getCascadeBoundingBox()
if not cc.rectContainsPoint(cascadeBound, cc.p(event.x, event.y)) then
return false
end
end
if "began" == event.name then
self.prevX_ = event.x
self.prevY_ = event.y
self.bDrag_ = false
local x,y = self.scrollNode:getPosition()
self.position_ = {x = x, y = y}
transition.stopTarget(self.scrollNode)
self:callListener_{name = "began", x = event.x, y = event.y}
self:enableScrollBar()
-- self:changeViewRectToNodeSpaceIf()
self.scaleToWorldSpace_ = self:scaleToParent_()
return true
elseif "moved" == event.name then
if self:isShake(event) then
return
end
self.bDrag_ = true
self.speed.x = event.x - event.prevX
self.speed.y = event.y - event.prevY
if self.direction == UIScrollView.DIRECTION_VERTICAL then
self.speed.x = 0
elseif self.direction == UIScrollView.DIRECTION_HORIZONTAL then
self.speed.y = 0
else
-- do nothing
end
self:scrollBy(self.speed.x, self.speed.y)
self:callListener_{name = "moved", x = event.x, y = event.y}
elseif "ended" == event.name then
if self.bDrag_ then
self.bDrag_ = false
self:scrollAuto()
-- self:autoFixScroll()
self:callListener_{name = "ended", x = event.x, y = event.y}
self:disableScrollBar()
else
self:callListener_{name = "clicked", x = event.x, y = event.y}
end
end
end
function ShipListView:scrollAuto()
local status = self:twiningScroll()
if status == "normal" then
self:elasticScroll(true)
elseif status == "sideShow" then
self:elasticScroll(false)
end
end
function ShipListView:twiningScroll()
if self:isSideShow() then
-- printInfo("UIScrollView - side is show, so elastic scroll")
return "sideShow"
end
if math.abs(self.speed.x) < 10 and math.abs(self.speed.y) < 10 then
-- printInfo("#DEBUG, UIScrollView - isn't twinking scroll:"
-- .. self.speed.x .. " " .. self.speed.y)
return "normal"
end
local disX, disY = self:moveXY(0, 0, self.speed.x*6, self.speed.y*6)
transition.moveBy(self.scrollNode,
{x = disX, y = disY, time = 0.3,
easing = "sineOut",
onComplete = function()
self:elasticScroll(true)
end})
end
function ShipListView:elasticScroll(fix)
local cascadeBound = self:getScrollNodeRect()
local disX, disY = 0, 0
local viewRect = self:getViewRectInWorldSpace()
-- dump(cascadeBound, "UIScrollView - cascBoundingBox:")
-- dump(viewRect, "UIScrollView - viewRect:")
if cascadeBound.width < viewRect.width then
disX = viewRect.x - cascadeBound.x
else
if cascadeBound.x > viewRect.x then
disX = viewRect.x - cascadeBound.x
elseif cascadeBound.x + cascadeBound.width < viewRect.x + viewRect.width then
disX = viewRect.x + viewRect.width - cascadeBound.x - cascadeBound.width
end
end
if cascadeBound.height < viewRect.height then
disY = viewRect.y + viewRect.height - cascadeBound.y - cascadeBound.height
else
if cascadeBound.y > viewRect.y then
disY = viewRect.y - cascadeBound.y
elseif cascadeBound.y + cascadeBound.height < viewRect.y + viewRect.height then
disY = viewRect.y + viewRect.height - cascadeBound.y - cascadeBound.height
end
end
if 0 == disX and 0 == disY then
if fix then
self:autoFixScroll()
end
return
end
transition.moveBy(self.scrollNode,
{x = disX, y = disY, time = 0.3,
easing = "backout",
onComplete = function()
self:callListener_{name = "scrollEnd"}
end})
end
return ShipListView
```
**
**
**
**使用方式如下:**
**
**
local ShipListView = require("app.views.ShipListView")
local MainScene = class("MainScene", function()
return display.newScene("MainScene")
end)
function MainScene:ctor()
self.shipList1 = ShipListView.new({
bgColor = cc.c4b(200, 200, 200, 120),
viewRect = cc.rect(display.cx - 240, display.cy + 28, 520, 150),
direction = cc.ui.UIScrollView.DIRECTION_HORIZONTAL},
self)
:onTouch(handler(self, self.touchListener))
:addTo(self)
for i=1,100 do
local item = self.shipList1:newItem()
local content = display.newSprite("flattop.png")
item:addContent(content)
item:setItemSize(104, 150)
self.shipList1:addItem(item)
end
self.shipList1:reload()
self.shipList1:checkArrowStatus()
end
function MainScene:touchListener(event)
local listView = event.listView
listView:checkArrowStatus()
if "clicked" == event.name then
-- TODO:
elseif "moved" == event.name then
-- TODO:
elseif "ended" == event.name then
-- TODO:
else
-- print("event name:" .. event.name)
end
end
```
因为是此篇教程属于非小白系列,所以不详细讲解每段代码内容,看官们应该一看便知,修改添加的部分没任何技术含量!



