quick高级应用之扩展listview

群里不止一次看到有人问如何让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



```


因为是此篇教程属于非小白系列,所以不详细讲解每段代码内容,看官们应该一看便知,修改添加的部分没任何技术含量!

先mark,明天在看。

先顶,顶完再看~~~~~~~~~~~~~~~~~~~~~~~~

先赞一个~:2:
42-43行,是符号写错了吗?

这里下图的判断里面,应该让最右边的宽度减一吧,不然那个点就永远在rect里面,右边的箭头就一直显示


我这边倒是没有-1也正常,这个跟设置的item宽度有关系,如果是恰好整除应该就不用-1

:801: :801: :801:

刀哥V5 话说你那图是飞船?

恩,一款太空战游戏里的图片

:891: 三刀V5