UI Scrollable List

From GiderosMobile
Revision as of 15:32, 13 July 2023 by Hgy29 (talk | contribs) (Text replacement - "<source" to "<syntaxhighlight")


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

Ui list view.png

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>

Template:Welcome!