ScrollView and scaling middle Item

Hi There,

I’ve create a scrollView with Buttons, until here everything’s working.

What I try to do is Scale the middle button and when I swipe, the button in the middle scale up and the other ones scale down and if you swipe between 2 objects the closest object goes automaticaly in the middle (as you can see in this game shop https://www.youtube.com/watch?v=UWdrQgt7Mmc or in FUN RUN https://www.youtube.com/watch?v=Yug6KmL9Re0 (at 2minute you see the shop)

I’ve tried to accomplish it with the scrollview Listener but is not perfect, I can’t know which buttons is in middle because all buttons are integrated in the scrollView so the x coordonate are not changing, so I’m trying to make my own scrollView to resolve this issue but is not easy at all…

If someone can enlighten me, because I’m a bit lost, I can’t figure out where I have to start for …

thanks you, Julio

This is a tricky one and I would not use a scrollview for this myself, but if you do, then you will need to use an enterFrame listener to make sure that you are checking the position of each item in the view.

So, while the scrollview is on the screen, use an enterFrame function to loop over each item and check it’s real screen position (you can use either the scrollview scroll position or localToContent() ) and scale each item in the scrollview based on how close it is to the centre of the screen.

 Thanks horacebury, I did not know about the localToContent, it helps me a lot for now !

How would you do for yourself ?

Something like this:

local widget = require( "widget") local centreX, scaleWithinDist, minScale, maxScale = display.actualContentWidth \* .5, display.actualContentWidth \* .2, .5, 1.5 local scaleDiff = maxScale - minScale local function populateScrollItems( view ) -- you need to code this end local scrollView = widget.newScrollView { top = display.actualContentHeight \* .25, left = 0, width = display.actualContentWidth, height = display.actualContentHeight \* .5, scrollWidth = display.actualContentWidth, scrollHeight = display.actualContentHeight, verticalScrollDisabled = true, listener = scrollListener, } local function scaleScrollItems() local view = scrolllView:getView() for i=1, view.numChildren do local item = view[i] local x, y = item:localToContent( x, y ) local dist = math.abs( centreX - x ) if (dist \> scaleWithinDist) then item.xScale, item.yScale = minScale, minScale else local factor = 1 - (dist / scaleWithinDist) local scale = minScale + factor \* scaleDiff item.xScale, item.yScale = scale, scale end end end local function scrollListener( e ) if (e.phase == "began") then Runtime:addEventListener( "enterFrame", scaleScrollItems ) elseif (e.phase == "ended") then timer.performWithDelay( 500, function() Runtime:removeEventListener( "enterFrame", scaleScrollItems ) end, 1 ) end return true end populateScrollItems( scrolllView:getView() )  

Word of warning: I have not tested that code. It is straight from my head and has not been executed even once.

Let me know if you have questions, but I believe the logic is sound.

Thanks a ton dude !

I like the wave effect. So I managed it a bit to get what I needed with auto-positioning and it look pretty well. 

I did not use the  " view = scrolllView:getView() "  but rather a common table including all my stuff because I don’t control it, to what i’ve understood is the scrollView table, isnt it ?

so I publish my code if it can help someone else.

local screen = require "Data.screenData" local myData = require "Data.myData" local widget = require "widget" --"here there're all my object inserted from an other page" local objShop = {} local levelSelectGroup local scaleWithinDist = display.actualContentWidth \* .2 local minScale, maxScale = 0.8 ,1.6 local scaleDiff = maxScale - minScale local boutiqueFrame = function() local velocity = math.abs(levelSelectGroup.\_view.\_velocity) --print(velocity, "velocity") for j = 1,#objShop do local item = objShop[j] if item ~= nil then --"scaling object" local x, y = item:localToContent( 0, 0 ) local dist = math.abs( screen.centerX - x ) if (dist \> scaleWithinDist) then item.xScale, item.yScale = minScale, minScale else local factor = 1 - (dist / scaleWithinDist) local scale = minScale + factor \* scaleDiff item.xScale, item.yScale = scale, scale end --"auto-positioning object in the middle" if item.xScale \>= 1.27 and item.xScale \< maxScale then local itemIdx = j-1 local between = (objShop[2].x - objShop[1].x) if velocity \<= 1.3 and not myData.velocity and not myData.scrollTo then myData.scrollTo = true myData.velocity = true levelSelectGroup:scrollToPosition {x = -between \* itemIdx, time = 190, onComplete = function()myData.scrollTo = false end} end end end end end local function scrollListener( event ) local phase = event.phase if ( phase == "began" ) then myData.velocity = true elseif ( phase == "moved" ) then elseif ( phase == "ended" or phase == "cancelled" ) then myData.velocity = false end if ( event.limitReached ) then if ( event.direction == "left" ) then levelSelectGroup.\_view.\_velocity = 0 elseif ( event.direction == "right" ) then levelSelectGroup.\_view.\_velocity = 0 end end return true end levelSelectGroup = widget.newScrollView({ width = display.contentWidth, height = display.contentHeight, isLocked = false, friction = 0.976, isBounceEnabled = true, hideBackground = true, hideScrollBar = false, verticalScrollDisabled = true, listener = scrollListener }) levelSelectGroup.x = display.contentCenterX levelSelectGroup.y = display.contentCenterY Runtime:addEventListener("enterFrame", boutiqueFrame )

Last thing, Do you know if you can cancel the scrollToPosition, I tried to do " transition.cancel() " but it does no effect, so if I increase his time it makes me some bug because the transition is not done. 

Hope that could help ! if you have some advices to improve it, I get them.

Why are you even checking velocity? enterFrame is called on every frame, so you don’t need to do anything with velocity because the scale of your items in the scrollView are updated while the scrollView is being scrolled. I just added a timer to continue updating it if there is any inertia causing the scroll to continue after the touch is released.

You seem to be trying to calculate the inertia yourself. Don’t do this. Here be dragons and it will not end well.

Also, you are calling scrollToPosition inside an enterFrame listener. There is no way this will end well. Simply never do this.

What is the problem that your changes are trying to solve?

I’m programming for not even 6 month so I’m pretty new… it was working almost nicely for myself like that, but I know it’s not as it should be code.

I check the velocity, because if the velocity is high I don’t want to auto-positioning the object but if the velocity is low (so it depend to the swipe) I auto-position the closest object from the middle in the middle.  Like that, I can swipe short or move until the next object and see object by object or swipe longer to go far away in the scrollview. (like the video of ski safari shop I put in the first post)

Do you know what I mean ?

Yes, and this is a problem. What you’re describing is a stepped scrolling action. I think you’re on the right track, but I’ve not seen this solution work yet.

This is a tricky one and I would not use a scrollview for this myself, but if you do, then you will need to use an enterFrame listener to make sure that you are checking the position of each item in the view.

So, while the scrollview is on the screen, use an enterFrame function to loop over each item and check it’s real screen position (you can use either the scrollview scroll position or localToContent() ) and scale each item in the scrollview based on how close it is to the centre of the screen.

 Thanks horacebury, I did not know about the localToContent, it helps me a lot for now !

How would you do for yourself ?

Something like this:

local widget = require( "widget") local centreX, scaleWithinDist, minScale, maxScale = display.actualContentWidth \* .5, display.actualContentWidth \* .2, .5, 1.5 local scaleDiff = maxScale - minScale local function populateScrollItems( view ) -- you need to code this end local scrollView = widget.newScrollView { top = display.actualContentHeight \* .25, left = 0, width = display.actualContentWidth, height = display.actualContentHeight \* .5, scrollWidth = display.actualContentWidth, scrollHeight = display.actualContentHeight, verticalScrollDisabled = true, listener = scrollListener, } local function scaleScrollItems() local view = scrolllView:getView() for i=1, view.numChildren do local item = view[i] local x, y = item:localToContent( x, y ) local dist = math.abs( centreX - x ) if (dist \> scaleWithinDist) then item.xScale, item.yScale = minScale, minScale else local factor = 1 - (dist / scaleWithinDist) local scale = minScale + factor \* scaleDiff item.xScale, item.yScale = scale, scale end end end local function scrollListener( e ) if (e.phase == "began") then Runtime:addEventListener( "enterFrame", scaleScrollItems ) elseif (e.phase == "ended") then timer.performWithDelay( 500, function() Runtime:removeEventListener( "enterFrame", scaleScrollItems ) end, 1 ) end return true end populateScrollItems( scrolllView:getView() ) &nbsp;

Word of warning: I have not tested that code. It is straight from my head and has not been executed even once.

Let me know if you have questions, but I believe the logic is sound.

Thanks a ton dude !

I like the wave effect. So I managed it a bit to get what I needed with auto-positioning and it look pretty well. 

I did not use the  " view = scrolllView:getView() "  but rather a common table including all my stuff because I don’t control it, to what i’ve understood is the scrollView table, isnt it ?

so I publish my code if it can help someone else.

local screen = require "Data.screenData" local myData = require "Data.myData" local widget = require "widget" --"here there're all my object inserted from an other page" local objShop = {} local levelSelectGroup local scaleWithinDist = display.actualContentWidth \* .2 local minScale, maxScale = 0.8 ,1.6 local scaleDiff = maxScale - minScale local boutiqueFrame = function() local velocity = math.abs(levelSelectGroup.\_view.\_velocity) --print(velocity, "velocity") for j = 1,#objShop do local item = objShop[j] if item ~= nil then --"scaling object" local x, y = item:localToContent( 0, 0 ) local dist = math.abs( screen.centerX - x ) if (dist \> scaleWithinDist) then item.xScale, item.yScale = minScale, minScale else local factor = 1 - (dist / scaleWithinDist) local scale = minScale + factor \* scaleDiff item.xScale, item.yScale = scale, scale end --"auto-positioning object in the middle" if item.xScale \>= 1.27 and item.xScale \< maxScale then local itemIdx = j-1 local between = (objShop[2].x - objShop[1].x) if velocity \<= 1.3 and not myData.velocity and not myData.scrollTo then myData.scrollTo = true myData.velocity = true levelSelectGroup:scrollToPosition {x = -between \* itemIdx, time = 190, onComplete = function()myData.scrollTo = false end} end end end end end local function scrollListener( event ) local phase = event.phase if ( phase == "began" ) then myData.velocity = true elseif ( phase == "moved" ) then elseif ( phase == "ended" or phase == "cancelled" ) then myData.velocity = false end if ( event.limitReached ) then if ( event.direction == "left" ) then levelSelectGroup.\_view.\_velocity = 0 elseif ( event.direction == "right" ) then levelSelectGroup.\_view.\_velocity = 0 end end return true end levelSelectGroup = widget.newScrollView({ width = display.contentWidth, height = display.contentHeight, isLocked = false, friction = 0.976, isBounceEnabled = true, hideBackground = true, hideScrollBar = false, verticalScrollDisabled = true, listener = scrollListener }) levelSelectGroup.x = display.contentCenterX levelSelectGroup.y = display.contentCenterY Runtime:addEventListener("enterFrame", boutiqueFrame )

Last thing, Do you know if you can cancel the scrollToPosition, I tried to do " transition.cancel() " but it does no effect, so if I increase his time it makes me some bug because the transition is not done. 

Hope that could help ! if you have some advices to improve it, I get them.

Why are you even checking velocity? enterFrame is called on every frame, so you don’t need to do anything with velocity because the scale of your items in the scrollView are updated while the scrollView is being scrolled. I just added a timer to continue updating it if there is any inertia causing the scroll to continue after the touch is released.

You seem to be trying to calculate the inertia yourself. Don’t do this. Here be dragons and it will not end well.

Also, you are calling scrollToPosition inside an enterFrame listener. There is no way this will end well. Simply never do this.

What is the problem that your changes are trying to solve?

I’m programming for not even 6 month so I’m pretty new… it was working almost nicely for myself like that, but I know it’s not as it should be code.

I check the velocity, because if the velocity is high I don’t want to auto-positioning the object but if the velocity is low (so it depend to the swipe) I auto-position the closest object from the middle in the middle.  Like that, I can swipe short or move until the next object and see object by object or swipe longer to go far away in the scrollview. (like the video of ski safari shop I put in the first post)

Do you know what I mean ?

Yes, and this is a problem. What you’re describing is a stepped scrolling action. I think you’re on the right track, but I’ve not seen this solution work yet.