So I solved the issue. I ended up using a moving group with the touch handlers. It is not perfect like facebook, but I think its as good as the scrolling done by Linkedin's app.
To create momentum, I modified the animation engine to have a new handler called "decelerate". aeMoveTo and aeGeneral have to modified as well.
The next issue was to understand the way people were using their finger to gently and briefly swipe the screen. The problem with this is that the sample rate from the touchMove handler or the MouseMove handler are about 130 milliseconds which is in no way possible on a touch that is less than that. To overcome this I created a repeat loop that samples and builds a list that goes for about 110 milliseconds or so with 10 datapoints, then used that to calculate an initial velocity.
I kept the equations for distance and velocity simple by using physics equations rather than calculus. They are: X = Xi + Vo * t - (0.5) * a * t ^ 2 and Vf = sqrt(Vo^2 - 2 * a * X)
Below I will post the scripts I used to get the mobile scroller to scroll smoothly while accepting light and quick swipes.
Group Script:
Code: Select all
local sTracking, yMousei, yLoci, xLoci, sIsScrolling, tUpperBound, tLowerBound
local sIsMoving, sTouchEnded, sMovement
constant kTouchSensitivity = 5 --minimum pixels on a swipe-tap
constant kMoveSensitivity = 0.06 --minimum velocity to create momentum
constant kScreenSwipeTime = 250 --number of milliseconds that are considered a long scroll rather than a swipe
on Momentum
if sIsScrolling is "true" then -- from the initial touch
put "false" into sIsScrolling
put "false" into sIsMoving
##calculate the speed
if abs((item 2 of line 1 of sTracking) - (item 2 of the last line of sTracking)) >= kScreenSwipeTime then --total screen touch time big
put "Scrolling" & cr after sMovement
if ((item 2 of line 1 of sTracking) - (item 2 of line 3 of sTracking)) <> 0 then --Dont move if stopped
put ((item 1 of line 1 of sTracking) - (item 1 of line 3 of sTracking)) / \
((item 2 of line 1 of sTracking) - (item 2 of line 3 of sTracking)) into tInitialVelocity
if abs(tInitialVelocity) > kMoveSensitivity then --No micro adjustments
put "true" into sIsMoving
aeMoveTo (the long id of me), (item 1 of the loc of me), 1, 20000, "decelerate", tInitialVelocity, tUpperBound, tLowerBound
end if
end if
end if
##Makes sure that swipe gets sent too if sScrolling is true, and touch is released
Swipe
end if
end Momentum
on Swipe
if sIsMoving is "false" then
##Swipe --Not caught by the handlers
if line 10 of sMovement is empty then exit Swipe
if abs((item 1 of line 1 of sMovement) - (item 1 of line 10 of sMovement)) > kTouchSensitivity then
put ((item 1 of line 1 of sMovement) - (item 1 of line 10 of sMovement)) / \
((item 2 of line 1 of sMovement) - (item 2 of line 10 of sMovement)) into tInitialVelocity
put "true" into sIsMoving
aeMoveTo (the long id of me), (item 1 of the loc of me), 1, 20000, "decelerate", (1.3 * tInitialVelocity), tUpperBound, tLowerBound
end if
end if
end Swipe
on aeMoveDone
put "false" into sIsMoving
ChangeMovingStatus "false"
end aeMoveDone
on TouchRelease
Momentum
end TouchRelease
on TouchStart
global gGap
if sIsMoving is "true" then
aeCancel
end if
ChangeMovingStatus "true"
put "false" into sIsMoving
put empty into sTracking
put the mouseV into yMousei
put item 1 of the loc of me into xLoci
put item 2 of the loc of me into yLoci
put "true" into sIsScrolling
put the bottom of grp "Nav Bar" of this card + (0.5 * the height of me) into tUpperBound
put the bottom of this card - (0.5 * the height of me) into tLowerBound
startRecording
end TouchStart
on TouchMove pID, pX, pY
if sIsScrolling is "true" then
if the mouse is down then
put the mouseV - yMousei into tDelta
##Create lower and upper boundaries
if item 2 of the loc of me + tDelta > tUpperBound then
set the loc of me to xLoci, tUpperBound
else if item 2 of the loc of me + tDelta < tLowerBound then
set the loc of me to xLoci, tLowerBound
else
set the loc of me to xLoci, yLoci + tDelta
end if
put tDelta & comma & the milliseconds & cr before sTracking
else
Momentum
end if
end if
end TouchMove
on TouchEnd
Momentum
end TouchEnd
on StartRecording
put empty into sMovement
repeat with i = 1 to 10
put the mouseV & comma & the milliseconds & cr before sMovement
wait 10 milliseconds with messages
end repeat
if abs((item 1 of line 1 of sMovement) - (item 1 of line 10 of sMovement)) > kTouchSensitivity then
if sIsScrolling is "false" then
put "false" into sMoving
swipe
end if
end if
end StartRecording
Here is the AE General handler rewritten for deceleration
Code: Select all
command aeGeneral
lock screen
local tTimeLost,tFramesLost,tElapsed
local tX,tY,tDuration,tMethod,tEndX,tStartX,tDestX,tStartY,tDestY,tStartTime,tStartVelocity,tDirection
local tUpperBound,tLowerBound
##Stops the move immediately
if tCancel is "true" then
put "false" into tCancel
repeat for each line pControl in the keys of tAEEasing
send "aeMoveDone" to pControl
end repeat
exit aeGeneral
end if
if sAEFrameRate is not a number then aeResetFrameRate
put the milliseconds into tElapsed
put the milliseconds - sTimeTaken into tTimeLost
put tTimeLost - (1000/ sAEFrameRate) into tTimeLost
put ((1000/sAeFramerate) + tTimeLost) into tFramesLost
if tFramesLost<>0 then
put 1000/tFramesLost into tFramesLost
end if
if tFramesLost>sAEFrameRate then put sAEFrameRate into tFramesLost
put tFramesLost into sAERealFrameRate
put the millisecs into sTimeTaken
--lock screen
repeat for each line pControl in the keys of tAEEasing
send "aeEnterFrame" && "aeMoveTo" to pcontrol
put item 1 of tAEEasing[pControl] into tDestX
put item 2 of tAEEasing[pControl] into tDestY
put item 3 of tAEEasing[pControl] into tDuration
put item 4 of tAEEasing[pControl] into tMethod
put item 5 of tAEEasing[pControl] into tStartX
put item 6 of tAEEasing[pControl] into tStartY
put item 7 of tAEEasing[pControl] into tStartTime
put item 8 of tAEEasing[pControl] into tStartVelocity
put item 9 of tAEEasing[pControl] into tUpperBound
put item 10 of tAEEasing[pControl] into tLowerBound
##Determine if the velocity is negative or positive
if tStartVelocity > 0 then
put "down" into tDirection
put tStartY + ((tStartVelocity ^ 2) * 0.5 * (1/sAcceleration)) - 5 into tDestY
else if tStartVelocity < 0 then
put "up" into tDirection
put tStartY + ((-1) * ((tStartVelocity ^ 2) * 0.5 * (1/sAcceleration))) + 5 into tDestY
end if
if tStartTime="pending" then next repeat
switch tMethod
case "in"
put aeEaseIn(tStartX,tDestX,tDuration,the milliseconds-tStartTime,3) into tX
put aeEaseIn(tStartY,tDestY,tDuration,the milliseconds-tStartTime,3) into ty
break
case "out"
put aeEaseOut(tStartX,tDestX,tDuration,the milliseconds-tStartTime,aeExponent) into tX
put aeEaseOut(tStartY,tDestY,tDuration,the milliseconds-tStartTime,aeExponent) into ty
break
case "decelerate"
-- put aeDecelerate(tStartX,the milliseconds-tStartTime,tStartVelocity) into tX
put tStartX into tX
put aeDecelerate(tStartY,the milliseconds-tStartTime,tStartVelocity,tDirection,tDestY,tUpperBound,tLowerBound) into ty
break
case "inOut"
put aeEaseInOut(tStartX,tDestX,tDuration,the milliseconds-tStartTime,3) into tX
put aeEaseInOut(tStartY,tDestY,tDuration,the milliseconds-tStartTime,3) into ty
break
case "bounce"
put aeBounceEaseOut(tStartX,tDestX,tDuration,the milliseconds-tStartTime) into tX
put aeBounceEaseOut(tStartY,tDestY,tDuration,the milliseconds-tStartTime) into ty
break
case "overshoot"
put aeOverShootEaseOut(tStartX,tDestX,tDuration,the milliseconds-tStartTime) into tX
put aeOverShootEaseOut(tStartY,tDestY,tDuration,the milliseconds-tStartTime) into ty
break
default
put aeEaseIn(tStartX,tDestX,tDuration,the milliseconds-tStartTime,1) into tX
put aeEaseIn(tStartY,tDestY,tDuration,the milliseconds-tStartTime,1) into ty
break
end switch
if the milliseconds-tStartTime<tDuration then
set the loc of pControl to tX,tY
else
if tMethod is not "decelerate" then
if tDestx,tDestY is a point then
set the loc of pControl to tDestX,tDestY
end if
else
##Case decelerate ending
end if
delete variable tAeEasing[pControl]
send "aeMoveDone" to pControl
end if
send "aeExitFrame" && "aeMoveTo" to pControl
end repeat
-- unlock screen
unlock screen
put the milliseconds-tElapsed into tElapsed
if the keys of tAeEasing is not empty then
if tElapsed < 1000 / sAEFrameRate then
if "aeGeneral" is not in the pendingmessages then
send "aeGeneral" to me in (1000/sAEFRameRate)- tElapsed millisecs
end if
else
if "aeGeneral" is not in the pendingmessages then
send "aeGeneral" to me in 5 millisecs
end if
end if
end if
-- wait 0 milliseconds with messages
end aeGeneral
Here is the AE MoveTo handler rewritten for deceleration
Code: Select all
on aeMoveTo
local tControl,tX,tY,tDuration,tMethod,theValue,tVelocity,tUpperBound,tLowerBound
if sAEFrameRate is empty then aeResetFrameRate
repeat with i=1 to paramcount()
if i<paramcount() then
put param(i)&"," after theValue
else
put param(i) after theValue
end if
end repeat
put item 1 of theValue into tControl
put item 2 of theValue into tX
put item 3 of theValue into tY
put item 4 of theValue into tDuration
put item 5 of theValue into tMethod
put item 6 of theValue into tVelocity
put item 7 of theValue into tUpperBound
put item 8 of theValue into tLowerBound
if there is no tControl then return "error:"&&tControl&&"is not a valid control or group or stack"
if word 1 of the long name of tControl is "card" then return "error: can not move a card"
if tX is not an integer then return "error:"&&tX&","&tY&&"is not a valid point"
if tY is not an integer then return "error:"&&tX&","&tY&&"is not a valid point"
if tduration is not a number then return "error:"&&tDuration&&"is not a valid duration"
if tMethod is not among the items of "in,out,inout,bounce,overshoot,decelerate" then return "error:"&&tDuration&&"is not a valid easing method. Must be in,out,inout,bounce,overshoot,decelerate"
if not sAELockMoves then
put tX,tY,tDuration,tMethod,item 1 of the the loc of tControl,item 2 of the loc of tControl,the milliseconds,tVelocity,tUpperBound,tLowerBound into tAeEasing[the long id of tControl]
if "aeGeneral" is not in the pendingmessages then
aeGeneral
end if
else
put tX,tY,tDuration,tMethod,item 1 of the the loc of tControl,item 2 of the loc of tControl,"pending",tVelocity,tUpperBound,tLowerBound into tAeEasing[the long id of tControl]
end if
end aeMoveTo
Here is a handler that I wrote to stop the momentum when a new touch is placed on the group
Code: Select all
local aeExponent, tCancel
command SetAEExponent pExponent
put pExponent into aeExponent
end SetAEExponent
command aeCancel
put "true" into tCancel
end aeCancel
Here is the AE Deceleration Handler that I wrote
Code: Select all
function aeDecelerate pStart,pElapsedTime,pStartVelocity,pDirection,pFinish,pUpperBound,pLowerBound
try
if pDuration<=pElapsedTime then return pEnd
if pElapsedTime<=0 then return pStart
if pDirection = "down" then
put pStart + pStartVelocity * pElapsedTime - (0.5 * (sAcceleration) * ((pElapsedTime) ^ 2)) into Xf
if pFinish <= Xf or pUpperBound < Xf then
aeCancel
end if
else if pDirection = "up" then
put pStart + pStartVelocity * pElapsedTime - (0.5 * ((-1) * sAcceleration) * ((pElapsedTime) ^ 2)) into Xf
if pFinish > Xf or pLowerBound > Xf then
aeCancel
end if
end if
return Xf
catch theError
return theError
end try
end aeDecelerate
On the stack I preopened with this:
Code: Select all
on preOpenStack
set the compositorTileSize of this stack to 128
set the compositorType of this stack to "software"
set the acceleratedrendering of me to true
set the fullscreenmode of me to "ShowAll"
go to card "Edit Event"
end preOpenStack
I also set the layermode of all the objects within the group to "dynamic"
BTW If you dont have the Animation Engine, give up livecoding now because you are making it way more difficult than it needs to be. The animation engine is a must have for anyone that is not an expert programmer and it is opensource and able to be modified. My code above only works with the Animation Engine open in Livecode. Well worth every penny.
I hope this helps anyone else who has been frustrated with trying to get smooth and responsive scrolling of large groups on mobile devices. Old skoolers, please feel free to contribute if you have any improvements!
-Will