UI Scrollable List
Here you will find various resources to help you create scrollable lists in Gideros Studio.
note:You may have to provide your own assets (fonts, gfx, …).
Scrollable List
Class
<syntaxhighlight lang="lua"> --====================================================================-- -- ListView widget for GiderosMobile --====================================================================-- -- -- ListView.lua -- Version 0.1 beta -- Author: Lukas Gergel, http://pykaso.net -- -- This library is free to use and modify. Use it for free! -- -- TODO: add methods for programatic manipulating with ListView -- -- USAGE -- -- myList = ListView.new({ -- width=280, -- height=390, -- bgTexture = Texture.new("texture.png"), -- bgColor = 0xffffff, -- rowSnap = true, -- experimental feature -- friction = 0.92 -- number lower then 1 -- data=data -- })
ListView = Core.class(Shape)
function ListView:init(params) -- properties self.velocityPrevTime = 0 self.listHeight = 0
--data items (pre rendered sprites) local data = params.data or {}
--configuration self.cfg = { --dimension width = params.width or self.screenW, height = params.height or self.screenH, bgColor = params.bgColor or nil, rowSnap = params.rowSnap or false, friction = params.friction or 0.92, callback = params.callback or function(row) return row end, onScroll = params.onScroll or nil }
local prevY, prevH, i, finalData = 0, 0, 0, {}
for i = 1, #data do finalData[i] = {} finalData[i].objectData = data[i] finalData[i].height = data[i]:getHeight() finalData[i].xInit = 0 finalData[i].yInit = prevY + prevH prevY = finalData[i].yInit prevH = finalData[i].height self.listHeight = self.listHeight + finalData[i].height end
if(self.cfg.bgColor ~= nil) then self:setFillStyle(Shape.SOLID, self.cfg.bgColor, 1) end
-- create defined shape self:beginPath() self:moveTo(0, 0) self:lineTo(0, self.cfg.height) self:lineTo(self.cfg.width, self.cfg.height) self:lineTo(self.cfg.width, 0) self:closePath() self:endPath()
-- create defined shape self.mask = Shape.new() self.mask:beginPath() self.mask:moveTo(0, 0) self.mask:lineTo(0, self.cfg.height) self.mask:lineTo(self.cfg.width, self.cfg.height) self.mask:lineTo(self.cfg.width, 0) self.mask:closePath() self.mask:endPath() self:addChild(self.mask)
self.listItems = Sprite.new() self:addChild(self.listItems) self.viewSize = self.cfg.height
self.itemData = finalData finalData = nil self:createRender() end
function ListView:newListItem(id, data) local thisItem = Sprite.new() local callback = self.cfg.callback local t = callback(data) local tv = self thisItem.id = id thisItem:addChild(t)
thisItem:addEventListener(Event.TOUCHES_BEGIN, function(e) if(self.mask:hitTestPoint(e.touch.x, e.touch.y)) then tv:listItemTouch(e, "begin") end end)
thisItem:addEventListener(Event.TOUCHES_MOVE, function(e) tv:listItemTouch(e,"move") end)
thisItem:addEventListener(Event.TOUCHES_END, function(e) tv:listItemTouch(e,"end") end)
return thisItem end
function ListView:listItemTouch(e, act) local li = self.listItems local top, height = self:getY(), self:getHeight()
if(act == "begin") then delta, velocity, prevPos = 0, 0, 0 self.isFocus = true
startPos = e.touch.y prevPos = e.touch.y
if self.tween then self.tween:setPaused(true) self.tween = nil end
self:removeEventListener("enterFrame", self.scrollList, self) self:addEventListener("enterFrame", self.trackVelocity, self)
elseif(self.isFocus) then if(act == "move") then local lastItem = li:getChildAt(li:getNumChildren()) delta = e.touch.y - prevPos prevPos = e.touch.y
if (li:getChildAt(1).id == 1 and li:getY() > 0) or (lastItem.id == #self.itemData and (self.listHeight - lastItem:getY() - lastItem:getHeight()) <= 0) then li:setY(math.floor(li:getY() + delta / 3)) else li:setY(math.floor(li:getY() + delta)) end self:updateRender() end
if(act == "end") then self:removeEventListener("enterFrame", self.trackVelocity, self) self:addEventListener("enterFrame", self.scrollList, self) self.isFocus = false end end end
function ListView:scrollList(event) if math.abs(velocity) < 0.3 then velocity = 0 self:removeEventListener("enterFrame", self.scrollList, self)
-- experimental feature if self.cfg.rowSnap then local _me = self local lastItem = self.listItems:getChildAt(self.listItems:getNumChildren()) local firstVisibleItem = self:getFirstVisibleRow()
if firstVisibleItem ~= nil and self.listItems:getY() < 0 and self.listHeight - lastItem:getY() - lastItem:getHeight() > 0 then local x, y = firstVisibleItem:getBounds(self) local vx, vy = self.listItems:localToGlobal(self.listItems:getBounds(self)) local final = self.listItems:getY() if firstVisibleItem:getHeight() + y >= firstVisibleItem:getHeight() / 2 then final = self.listItems:getY() - y else final = self.listItems:getY() - (firstVisibleItem:getHeight() + y) end self.tween = GTween.new(self.listItems, 0.7, {y = final}, {delay = 0.01, ease = easing.outQuartic, onChange = function() self:updateRender() end}) end else -- nothing here end end
local timePassed = event.deltaTime * 300 local li = self.listItems
-- Slow the list down velocity = velocity * self.cfg.friction li:setY(math.floor(self.listItems:getY() + velocity * timePassed))
self:updateRender()
local firstItem = li:getChildAt(1) local lastItem = li:getChildAt(li:getNumChildren()) local lx, ly = self:localToGlobal(li:getBounds(self)) local x, y, w, h = self:getBounds(self) local x2, y2 = self:localToGlobal(x, y) --local lx, ly = lastItem:getBounds(self)
if firstItem.id == 1 and li:getY() > 0 then velocity = 0 self:removeEventListener("enterFrame", self.scrollList, self) self.tween = GTween.new(li, 0.4, {y = li.yInit}, {delay = 0.01, ease = easing.outQuartic}) end
if lastItem.id == #self.itemData and self.listHeight - lastItem:getY() - lastItem:getHeight() <= 0 then if self.listHeight > self.viewSize and li:getY() < -self.listHeight + self.viewSize - lastItem:getHeight() * 0.5 then velocity = 0 self:removeEventListener("enterFrame", self.scrollList, self) self.tween = GTween.new(li, 0.4, {y = math.ceil(self.viewSize - self.listHeight)}, {delay = 0.01, ease = easing.outQuartic}) elseif self.listHeight < self.viewSize then velocity = 0 self:removeEventListener("enterFrame", self.scrollList, self) self.tween = GTween.new(li, 0.4, {y = li.yInit}, {delay = 0.01, ease = easing.outQuartic}) end end
return true end
function ListView:getFirstVisibleRow() local index = 1 local posY = self:getY() local item = nil
while posY <= self:getY() do if(index < self.listItems:getNumChildren()) then item = self.listItems:getChildAt(index) local x1, y1 = self:localToGlobal(item:getBounds(self)) posY = y1 + item:getHeight() index = index + 1 else return nil end end
return item end
function ListView:createRender() local lastY = 0 local position = 1 while lastY < self.viewSize and position <= #self.itemData do local rowObject = self:render(nil, position) if (rowObject == nil) then break end self.listItems:addChild(rowObject) rowObject:setPosition(self.itemData[position].xInit, self.itemData[position].yInit) lastY = rowObject:getY() position = position + 1 end self.listItems.yInit = self.listItems:getY() end
function ListView:render(thisItem, id) if thisItem == nil then thisItem = self:newListItem(id, self.itemData[id].objectData) else thisItem:removeChildAt(thisItem:getNumChildren()) local callback = self.cfg.callback local t = callback(self.itemData[id].objectData) thisItem:addChild(t) thisItem.id = id end
return thisItem end
function ListView:updateRender() local firstItem = self.listItems:getChildAt(1) local lastItem = self.listItems:getChildAt(self.listItems:getNumChildren()) local fx, fy = firstItem:getBounds(self) local lx, ly = lastItem:getBounds(self) local bottom = self.viewSize + self:getY() local fillToTop = fy > 0 local fillToBottom = ly + lastItem:getHeight() < self.viewSize
if fillToBottom then if (ly + lastItem:getHeight() < self.viewSize) and (lastItem.id ~= #self.itemData) then local y = lastItem:getY() + lastItem:getHeight() local recycledRow = firstItem self:render(recycledRow, lastItem.id + 1) self.listItems:addChild(recycledRow) recycledRow:setY(y) end
elseif fillToTop then if fy > 0 and (firstItem.id ~= 1) then local recycledRow = lastItem local newItem = self:render(recycledRow, firstItem.id - 1) local y = firstItem:getY() - newItem:getHeight() self.listItems:addChildAt(recycledRow, 1) recycledRow:setY(y) end end
if (self.cfg.onScroll ~= nil) then self.cfg.onScroll(self, firstItem) end end
function ListView:trackVelocity(event) local timePassed = msTimer() - self.velocityPrevTime self.velocityPrevTime = self.velocityPrevTime + timePassed
if prevY then velocity = (self.listItems:getY() - prevY) / timePassed end prevY = self.listItems:getY() end
function msTimer() return math.floor(os.timer() * 500) end
return ListView
</source>
Example
<syntaxhighlight lang="lua">
-- fonts
local font = TTFont.new("fonts/Roboto-Medium-webfont.ttf", 24)
local fontSub = TTFont.new("fonts/Roboto-Medium-webfont.ttf", 18)
-- rows layout function row(item) local row = Sprite.new()
local itembmp = Bitmap.new(Texture.new(item.icon)) itembmp:setScale(0.75) itembmp:setPosition(0, 0) row:addChild(itembmp)
local itemtitle = TextField.new(font, item.title) itemtitle:setPosition(itembmp:getX() + itembmp:getWidth() + 4, itembmp:getY() + itemtitle:getHeight()) row:addChild(itemtitle)
-- clickable button (arrow button) local arrow = Texture.new("gfx/arrow_right.png") local arrow_pressed = Texture.new("gfx/arrow_right_down.png") local button = Button.new(Bitmap.new(arrow), Bitmap.new(arrow_pressed)) button:setScale(3, 2) button:setPosition(application:getContentWidth() - button:getWidth(), itembmp:getY()) button:addEventListener("click", function() mytitle:setText(item.title) end) row:addChild(button)
return row end
-- datas local data = {} for i = 1, 10 do data[i] = row( { icon = "gfx/maurice.png", title = "Item ".. i, } ) end
-- scrollable list local myList = ListView.new( { width = application:getContentWidth() - 32, height = 8 * application:getContentHeight() / 10, friction = 0.99, bgColor = 0xaaaaff, rowSnap = true, -- experimental feature data = data } ) myList:setPosition(16, 0)
-- infos mytitle = TextField.new(nil, "CLICK AN ARROW") mytitle:setScale(3) mytitle:setTextColor(0xff0000) mytitle:setPosition(0, application:getContentHeight() - mytitle:getHeight())
-- stage
stage:addChild(myList)
stage:addChild(mytitle)
</source>