Hi,
The current scrollView widget has no support for dragging diagonally (or any direction), only horizontal or vertical.
Are there any third party options, or improved custom widgets to use for something like a worldmap (scrollview)?
Hi,
The current scrollView widget has no support for dragging diagonally (or any direction), only horizontal or vertical.
Are there any third party options, or improved custom widgets to use for something like a worldmap (scrollview)?
I think I fixed the problem!
I extended the original widget (as they are open-source) so it supports diagonal scrolling when both horizontal and vertical scrolling are not disabled.
I’ll share the code to help others out who find this topic
First download the open-source widget library: https://github.com/coronalabs/framework-widget and put it somewhere in your project folder.
Then add this piece of code to your main.lua to override the original widget library.
local function onRequireWidgetLibrary(name) return require("widget.widgetLibrary." .. name) end package.preload.widget = onRequireWidgetLibrary package.preload.widget\_momentumScrolling = onRequireWidgetLibrary package.preload.widget\_pickerWheel = onRequireWidgetLibrary package.preload.widget\_progressView = onRequireWidgetLibrary package.preload.widget\_scrollview = onRequireWidgetLibrary package.preload.widget\_searchField = onRequireWidgetLibrary package.preload.widget\_segmentedControl = onRequireWidgetLibrary package.preload.widget\_spinner = onRequireWidgetLibrary package.preload.widget\_stepper = onRequireWidgetLibrary package.preload.widget\_slider = onRequireWidgetLibrary package.preload.widget\_switch = onRequireWidgetLibrary package.preload.widget\_tabbar = onRequireWidgetLibrary package.preload.widget\_tableview = onRequireWidgetLibrary
Also take this code and override everything in widget/widgetLibrary/widget_momentumScrolling.lua
-- Copyright © 2013 Corona Labs Inc. All Rights Reserved. -- -- Redistribution and use in source and binary forms, with or without -- modification, are permitted provided that the following conditions are met: -- -- \* Redistributions of source code must retain the above copyright -- notice, this list of conditions and the following disclaimer. -- \* Redistributions in binary form must reproduce the above copyright -- notice, this list of conditions and the following disclaimer in the -- documentation and/or other materials provided with the distribution. -- \* Neither the name of the company nor the names of its contributors -- may be used to endorse or promote products derived from this software -- without specific prior written permission. -- \* Redistributions in any form whatsoever must retain the following -- acknowledgment visually in the program (e.g. the credits of the program): -- 'This product includes software developed by Corona Labs Inc. (http://www.coronalabs.com).' -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -- DISCLAIMED. IN NO EVENT SHALL CORONA LABS INC. BE LIABLE FOR ANY -- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. local M = {} -- Localize math functions local mAbs = math.abs local mFloor = math.floor -- configuration variables M.scrollStopThreshold = 250 -- direction variable that has a non-nil value only as long as the scrollview is scrolled M.\_direction = nil -- variable that establishes if the view limits were set ( they have to be called one time to position the scrollview correctly ) M.\_didSetLimits = false local isGraphicsV1 = ( 1 == display.getDefault( "graphicsCompatibility" ) ) -- Function to set the view's limits local function setLimits( self, view ) -- Set the bottom limit local bottomLimit = view.\_topPadding if isGraphicsV1 then bottomLimit = bottomLimit - view.\_height \* 0.5 end self.bottomLimit = bottomLimit -- TODO: use local functions for the limits instead of ifs -- Set the upper limit if view.\_scrollHeight then local upperLimit = ( -view.\_scrollHeight + view.\_height ) - view.\_bottomPadding -- the lower limit calculation is not necessary. We shift the view up with half its height, so the only thing we need to calculate -- is the upper limit. --if isGraphicsV1 then -- upperLimit = upperLimit - view.\_height \* 0.5 --end self.upperLimit = upperLimit end -- Set the right limit local rightLimit = view.\_leftPadding if isGraphicsV1 then rightLimit = rightLimit - view.\_width \* 0.5 end self.rightLimit = rightLimit -- Set the left limit if view.\_scrollWidth then local leftLimit = ( - view.\_scrollWidth + view.\_width ) - view.\_rightPadding if isGraphicsV1 then leftLimit = leftLimit - view.\_width \* 0.5 end self.leftLimit = leftLimit end end -- Function to handle vertical "snap back" on the view local function handleSnapBackVertical( self, view, snapBack ) -- Set the limits now setLimits( M, view ) local limitHit = "none" local bounceTime = 400 if not view.isBounceEnabled then bounceTime = 0 end -- Snap back vertically if not view.\_isVerticalScrollingDisabled then -- Put the view back to the top if it isn't already there ( and should be ), if we're not in a picker wheel if view.y \> self.bottomLimit or ( view.\_scrollHeight \< view.parent.height and not view.\_isUsedInPickerWheel ) then -- Set the hit limit limitHit = "bottom" -- Transition the view back to it's maximum position if "boolean" == type( snapBack ) then if snapBack == true then -- Ensure the scrollBar is at the bottom of the view if view.\_scrollBar then view.\_scrollBar:setPositionTo( "top" ) end -- Put the view back to the top view.\_tween = transition.to( view, { time = bounceTime, y = self.bottomLimit, transition = easing.outQuad } ) end end -- Put the view back to the bottom if it isn't already there ( and should be ) elseif view.y \< self.upperLimit then -- Set the hit limit limitHit = "top" -- Transition the view back to it's maximum position if "boolean" == type( snapBack ) then if snapBack == true then -- Ensure the scrollBar is at the bottom of the view if view.\_scrollBar then view.\_scrollBar:setPositionTo( "bottom" ) end -- Put the view back to the bottom view.\_tween = transition.to( view, { time = bounceTime, y = self.upperLimit, transition = easing.outQuad } ) end end end end return limitHit end -- Function to handle horizontal "snap back" on the view local function handleSnapBackHorizontal( self, view, snapBack ) -- Set the limits now setLimits( M, view ) local limitHit = "none" local bounceTime = 400 if not view.isBounceEnabled then bounceTime = 0 end -- Snap back horizontally if not view.\_isHorizontalScrollingDisabled then -- Put the view back to the left if it isn't already there ( and should be ) if view.x \< self.leftLimit then -- Set the hit limit limitHit = "left" -- Transition the view back to it's maximum position if "boolean" == type( snapBack ) then if snapBack == true then view.\_tween = transition.to( view, { time = bounceTime, x = self.leftLimit, transition = easing.outQuad } ) end end -- Put the view back to the right if it isn't already there ( and should be ) elseif view.x \> self.rightLimit then -- Set the hit limit limitHit = "right" -- Transition the view back to it's maximum position if "boolean" == type( snapBack ) then if snapBack == true then view.\_tween = transition.to( view, { time = bounceTime, x = self.rightLimit, transition = easing.outQuad } ) end end end end return limitHit end -- Function to clamp velocity to the maximum value local function clampVelocity( view ) -- Throttle the velocity if it goes over the max range if view.\_velocity\_h \< -view.\_maxVelocity then view.\_velocity\_h = -view.\_maxVelocity elseif view.\_velocity\_h \> view.\_maxVelocity then view.\_velocity\_h = view.\_maxVelocity end if view.\_velocity\_v \< -view.\_maxVelocity then view.\_velocity\_v = -view.\_maxVelocity elseif view.\_velocity\_v \> view.\_maxVelocity then view.\_velocity\_v = view.\_maxVelocity end end -- Handle momentum scrolling touch function M.\_touch( view, event ) local phase = event.phase local time = event.time local limit if "began" == phase then -- Reset values view.\_startXPos = event.x view.\_startYPos = event.y view.\_prevXPos = event.x view.\_prevYPos = event.y view.\_prevX = 0 view.\_prevY = 0 view.\_delta\_h = 0 view.\_delta\_v = 0 view.\_velocity\_h = 0 view.\_velocity\_v = 0 view.\_prevTime = 0 view.\_moveDirection = nil view.\_trackVelocity = true view.\_updateRuntime = false -- Set the limits now setLimits( M, view ) -- Cancel any active tween on the view if view.\_tween then transition.cancel( view.\_tween ) view.\_tween = nil end -- Set focus display.getCurrentStage():setFocus( event.target, event.id ) view.\_isFocus = true elseif view.\_isFocus then if "moved" == phase then -- Set the move direction if not view.\_moveDirection then local dx = mAbs( event.x - event.xStart ) local dy = mAbs( event.y - event.yStart ) local moveThresh = 12 if dx \> moveThresh or dy \> moveThresh then -- If there is a scrollBar, show it if view.\_scrollBar then -- Show the scrollBar, only if we need to (if the content height is higher than the view's height) -- TODO: when the diagonal scrolling comes to place, we have to treat the horizontal case as well here. if view.\_scrollBar.\_viewHeight \< view.\_scrollBar.\_viewContentHeight then view.\_scrollBar:show() end end if dx \> dy then -- If horizontal scrolling is enabled if not view.\_isHorizontalScrollingDisabled then -- The move was horizontal view.\_moveDirection = "horizontal" -- Handle vertical snap back handleSnapBackVertical( M, view, true ) end else -- If vertical scrolling is enabled if not view.\_isVerticalScrollingDisabled then -- The move was vertical view.\_moveDirection = "vertical" -- Handle horizontal snap back handleSnapBackHorizontal( M, view, true ) end end end end -- Horizontal movement --if "horizontal" == view.\_moveDirection then -- If horizontal scrolling is enabled if not view.\_isHorizontalScrollingDisabled then view.\_delta\_h = event.x - view.\_prevXPos view.\_prevXPos = event.x -- If the view is more than the limits if view.x \< M.leftLimit or view.x \> M.rightLimit then view.x = view.x + ( view.\_delta\_h \* 0.5 ) else view.x = view.x + view.\_delta\_h if view.\_listener and view.\_widgetType == "scrollView" then local actualDirection if view.\_delta\_h \< 0 then actualDirection = "left" elseif view.\_delta\_h \> 0 then actualDirection = "right" elseif view.\_delta\_h == 0 then if view.\_prevDeltaX and view.\_prevDeltaX \< 0 then actualDirection = "left" elseif view.\_prevDeltaX and view.\_prevDeltaX \> 0 then actualDirection = "right" end end -- if the scrollview is moving, assign the actual direction to the M.\_direction variable M.\_direction = actualDirection end end view.\_prevDeltaX = view.\_delta\_h if view.isBounceEnabled == true then -- if bounce is enabled and the view is used in picker, we snap back to prevent infinite scrolling if view.\_isUsedInPickerWheel == true then limit = handleSnapBackHorizontal( M, view, true ) else -- if not used in picker, we don't need snap back so we don't lose elastic behaviour on the tableview limit = handleSnapBackHorizontal( M, view, false ) end else limit = handleSnapBackHorizontal( M, view, true ) end end -- Vertical movement --else -- If vertical scrolling is enabled if not view.\_isVerticalScrollingDisabled then view.\_delta\_v = event.y - view.\_prevYPos view.\_prevYPos = event.y -- If the view is more than the limits if view.y \< M.upperLimit or view.y \> M.bottomLimit then view.y = view.y + ( view.\_delta\_v \* 0.5 ) -- shrink the scrollbar if the view is out of bounds if view.\_scrollBar then --view.\_scrollBar.yScale = 0.1 \* - ( view.y - M.bottomLimit ) end else view.y = view.y + view.\_delta\_v if view.\_listener and view.\_widgetType == "scrollView" then local actualDirection if view.\_delta\_v \< 0 then actualDirection = "up" elseif view.\_delta\_v \> 0 then actualDirection = "down" elseif view.\_delta\_v == 0 then if view.\_prevDeltaY and view.\_prevDeltaY \< 0 then actualDirection = "up" elseif view.\_prevDeltaY and view.\_prevDeltaY \> 0 then actualDirection = "down" end end -- if the scrollview is moving, assign the actual direction to the M.\_direction variable M.\_direction = actualDirection end end view.\_prevDeltaY = view.\_delta\_v -- Handle limits -- if bounce is true, then the snapback parameter has to be true, otherwise false if view.isBounceEnabled == true then -- if bounce is enabled and the view is used in picker, we snap back to prevent infinite scrolling if view.\_isUsedInPickerWheel == true then limit = handleSnapBackVertical( M, view, true ) else -- if not used in picker, we don't need snap back so we don't lose elastic behaviour on the tableview limit = handleSnapBackVertical( M, view, false ) end else limit = handleSnapBackVertical( M, view, true ) end -- Move the scrollBar if limit ~= "top" and limit ~= "bottom" then if view.\_scrollBar then view.\_scrollBar:move() end end -- Set the time held --view.\_timeHeld = time end --end elseif "ended" == phase or "cancelled" == phase then -- Reset values view.\_lastTime = event.time view.\_trackVelocity = false view.\_updateRuntime = true M.\_direction = nil -- we check if the view has a scrollStopThreshold value local stopThreshold = view.scrollStopThreshold or M.scrollStopThreshold if event.time - view.\_timeHeld \> stopThreshold then view.\_velocity\_h = 0 view.\_velocity\_v = 0 end view.\_timeHeld = 0 -- when tapping fast and the view is at the limit, the velocity changes sign. This ALWAYS has to be treated. if view.\_delta\_h \> 0 and view.\_velocity\_h \< 0 then view.\_velocity\_h = - view.\_velocity\_h end if view.\_delta\_h \< 0 and view.\_velocity\_h \> 0 then view.\_velocity\_h = - view.\_velocity\_h end if view.\_delta\_v \> 0 and view.\_velocity\_v \< 0 then view.\_velocity\_v = - view.\_velocity\_v end if view.\_delta\_v \< 0 and view.\_velocity\_v \> 0 then view.\_velocity\_v = - view.\_velocity\_v end -- Remove focus display.getCurrentStage():setFocus( nil ) view.\_isFocus = nil -- If on ended the scrollview is outside of the bounds, reposition it limit = handleSnapBackVertical( M, view, true ) end end end -- Handle runtime momentum scrolling events. function M.\_runtime( view, event ) local limit -- If we are tracking runtime if view.\_updateRuntime then local timePassed = event.time - view.\_lastTime view.\_lastTime = view.\_lastTime + timePassed -- Stop scrolling if velocity\_h is near zero if mAbs( view.\_velocity\_h ) \< 0.01 then view.\_velocity\_h = 0 -- Hide the scrollBar if view.\_scrollBar and view.autoHideScrollBar then view.\_scrollBar:hide() end end -- Stop scrolling if velocity\_v is near zero if mAbs( view.\_velocity\_v ) \< 0.01 then view.\_velocity\_v = 0 -- Hide the scrollBar if view.\_scrollBar and view.autoHideScrollBar then view.\_scrollBar:hide() end end if mAbs( view.\_velocity\_v ) \< 0.01 and mAbs( view.\_velocity\_h ) \< 0.01 then view.\_updateRuntime = false end -- Set the velocity view.\_velocity\_h = view.\_velocity\_h \* view.\_friction view.\_velocity\_v = view.\_velocity\_v \* view.\_friction -- Clamp the velocity if it goes over the max range clampVelocity( view ) -- Horizontal movement --if "horizontal" == view.\_moveDirection then -- If horizontal scrolling is enabled if not view.\_isHorizontalScrollingDisabled then -- Reset limit values view.\_hasHitLeftLimit = false view.\_hasHitRightLimit = false -- Move the view view.x = view.x + view.\_velocity\_h \* timePassed -- Handle limits if "horizontal" == view.\_moveDirection then limit = handleSnapBackHorizontal( M, view, true ) else limit = handleSnapBackHorizontal( M, view, true ) end -- Left if "left" == limit then -- Stop updating the runtime now view.\_updateRuntime = false -- If there is a listener specified, dispatch the event if view.\_listener then -- We have hit the left limit view.\_hasHitLeftLimit = true local newEvent = { direction = "left", limitReached = true, target = view, } view.\_listener( newEvent ) end -- Right elseif "right" == limit then -- Stop updating the runtime now view.\_updateRuntime = false -- If there is a listener specified, dispatch the event if view.\_listener then -- We have hit the right limit view.\_hasHitRightLimit = true local newEvent = { direction = "right", limitReached = true, target = view, } view.\_listener( newEvent ) end end end -- Vertical movement --else -- If vertical scrolling is enabled if not view.\_isVerticalScrollingDisabled then -- Reset limit values view.\_hasHitBottomLimit = false view.\_hasHitTopLimit = false -- Move the view view.y = view.y + view.\_velocity\_v \* timePassed -- Move the scrollBar if view.\_scrollBar then view.\_scrollBar:move() end -- Handle limits -- if we have motion, then we check for snapback. otherwise, we don't. if "vertical" == view.\_moveDirection then limit = handleSnapBackVertical( M, view, true ) else limit = handleSnapBackVertical( M, view, true ) end -- Top if "top" == limit then -- Hide the scrollBar if view.\_scrollBar and view.autoHideScrollBar then view.\_scrollBar:hide() end -- We have hit the top limit view.\_hasHitTopLimit = true -- Stop updating the runtime now view.\_updateRuntime = false -- If there is a listener specified, dispatch the event if view.\_listener then local newEvent = { direction = "up", limitReached = true, phase = event.phase, target = view, } view.\_listener( newEvent ) end -- Bottom elseif "bottom" == limit then -- Hide the scrollBar if view.\_scrollBar and view.autoHideScrollBar then view.\_scrollBar:hide() end -- We have hit the bottom limit view.\_hasHitBottomLimit = true -- Stop updating the runtime now view.\_updateRuntime = false -- If there is a listener specified, dispatch the event if view.\_listener then local newEvent = { direction = "down", limitReached = true, target = view, } view.\_listener( newEvent ) end end end --end end -- If we are tracking velocity if view.\_trackVelocity then -- Calculate the time passed local newTimePassed = event.time - view.\_prevTime view.\_prevTime = view.\_prevTime + newTimePassed -- Horizontal movement --if "horizontal" == view.\_moveDirection then -- If horizontal scrolling is enabled if not view.\_isHorizontalScrollingDisabled then if view.\_prevX then local possibleVelocity = ( view.x - view.\_prevX ) / newTimePassed if possibleVelocity ~= 0 then view.\_velocity\_h = possibleVelocity -- Clamp the velocity if it goes over the max range clampVelocity( view ) end end view.\_prevX = view.x end -- Vertical movement --elseif "vertical" == view.\_moveDirection then -- If vertical scrolling is enabled if not view.\_isVerticalScrollingDisabled then if view.\_prevY then local possibleVelocity = ( view.y - view.\_prevY ) / newTimePassed if possibleVelocity ~= 0 then view.\_velocity\_v = possibleVelocity -- Clamp the velocity if it goes over the max range clampVelocity( view ) end end view.\_prevY = view.y end --end end end -- Function to create a scrollBar function M.createScrollBar( view, options ) -- Require needed widget files local \_widget = require( "widget" ) local opt = {} local customOptions = options or {} -- Setup the scrollBar's width/height local parentGroup = view.parent.parent local scrollBarWidth = options.width or 5 local viewHeight = view.\_height -- The height of the windows visible area local viewContentHeight = view.\_scrollHeight -- The height of the total content height local minimumScrollBarHeight = 24 -- The minimum height the scrollbar can be -- Set the scrollbar Height local scrollBarHeight = ( viewHeight \* 100 ) / viewContentHeight -- If the calculated scrollBar height is below the minimum height, set it to it if scrollBarHeight \< minimumScrollBarHeight then scrollBarHeight = minimumScrollBarHeight end -- Grab the theme options for the scrollBar local themeOptions = \_widget.theme.scrollBar -- Get the theme sheet file and data opt.sheet = options.sheet opt.themeSheetFile = themeOptions.sheet opt.themeData = themeOptions.data opt.width = options.frameWidth or options.width or themeOptions.width opt.height = options.frameHeight or options.height or themeOptions.height -- Grab the frames opt.topFrame = options.topFrame or \_widget.\_getFrameIndex( themeOptions, themeOptions.topFrame ) opt.middleFrame = options.middleFrame or \_widget.\_getFrameIndex( themeOptions, themeOptions.middleFrame ) opt.bottomFrame = options.bottomFrame or \_widget.\_getFrameIndex( themeOptions, themeOptions.bottomFrame ) -- Create the scrollBar imageSheet local imageSheet if opt.sheet then imageSheet = opt.sheet else local themeData = require( opt.themeData ) imageSheet = graphics.newImageSheet( opt.themeSheetFile, themeData:getSheet() ) end -- The scrollBar is a display group M.scrollBar = display.newGroup() -- Create the scrollBar frames ( 3 slice ) M.topFrame = display.newImageRect( M.scrollBar, imageSheet, opt.topFrame, opt.width, opt.height ) if not isGraphicsV1 then M.topFrame.anchorX = 0.5; M.topFrame.anchorY = 0.5 end M.middleFrame = display.newImageRect( M.scrollBar, imageSheet, opt.middleFrame, opt.width, opt.height ) if not isGraphicsV1 then M.middleFrame.anchorX = 0.5; M.middleFrame.anchorY = 0.5 end M.bottomFrame = display.newImageRect( M.scrollBar, imageSheet, opt.bottomFrame, opt.width, opt.height ) if not isGraphicsV1 then M.bottomFrame.anchorX = 0.5; M.bottomFrame.anchorY = 0.5 end -- Set the middle frame's width M.middleFrame.height = scrollBarHeight - ( M.topFrame.contentHeight + M.bottomFrame.contentHeight ) -- Positioning M.middleFrame.y = M.topFrame.y + M.topFrame.contentHeight \* 0.5 + M.middleFrame.contentHeight \* 0.5 M.bottomFrame.y = M.middleFrame.y + M.middleFrame.contentHeight \* 0.5 + M.bottomFrame.contentHeight \* 0.5 -- Setup the scrollBar's properties M.scrollBar.\_viewHeight = viewHeight M.scrollBar.\_viewContentHeight = viewContentHeight M.scrollBar.alpha = 0 -- The scrollBar is invisible initally M.scrollBar.\_tween = nil -- function to recalculate the scrollbar params, based on content height change function M.scrollBar:repositionY() self.\_viewHeight = view.\_height self.\_viewContentHeight = view.\_scrollHeight -- Set the scrollbar Height local scrollBarHeight = ( self.\_viewHeight \* 100 ) / self.\_viewContentHeight -- If the calculated scrollBar height is below the minimum height, set it to it if scrollBarHeight \< minimumScrollBarHeight then scrollBarHeight = minimumScrollBarHeight end M.middleFrame.height = scrollBarHeight -- if we have topFrame and bottomFrame as non-collected objects, we use their dimensions to recalculate the position of the scrollbar if M.topFrame and M.topFrame.contentHeight and M.bottomFrame and M.bottomFrame.contentHeight then M.middleFrame.height = M.middleFrame.height - ( M.topFrame.contentHeight + M.bottomFrame.contentHeight ) -- Positioning of the middle and bottom frames according to the new scrollbar height M.middleFrame.y = M.topFrame.y + M.topFrame.contentHeight \* 0.5 + M.middleFrame.contentHeight \* 0.5 M.bottomFrame.y = M.middleFrame.y + M.middleFrame.contentHeight \* 0.5 + M.bottomFrame.contentHeight \* 0.5 end end -- Function to move the scrollBar function M.scrollBar:move() local viewY = view.y if isGraphicsV1 then viewY = viewY + view.parent.contentHeight \* 0.5 end local moveFactor = ( viewY \* 100 ) / ( self.\_viewContentHeight - self.\_viewHeight ) local moveQuantity = ( moveFactor \* ( self.\_viewHeight - self.contentHeight ) ) / 100 if viewY \< 0 then -- Only move if not over the bottom limit if mAbs( view.y ) \< ( self.\_viewContentHeight ) then self.y = view.parent.y - view.\_top - moveQuantity end end end function M.scrollBar:setPositionTo( position ) if "top" == position then self.y = view.parent.y - view.\_top elseif "bottom" == position then self.y = self.\_viewHeight - self.contentHeight end end -- Function to show the scrollBar function M.scrollBar:show() -- Cancel any previous transition if self.\_tween then transition.cancel( self.\_tween ) self.\_tween = nil end -- Set the alpha of the bar back to 1 self.alpha = 1 end -- Function to hide the scrollBar function M.scrollBar:hide() -- If there already isn't a tween in progress if not self.\_tween then self.\_tween = transition.to( self, { time = 400, alpha = 0, transition = easing.outQuad } ) end end -- Insert the scrollBar into the fixed group and position it view.\_fixedGroup:insert( M.scrollBar ) view.\_fixedGroup.x = view.\_width \* 0.5 - scrollBarWidth \* 0.5 --local viewFixedGroupY = view.parent.y - view.\_top - view.\_height \* 0.5 -- this has to be positioned at the yCoord - half the height, no matter what. local viewFixedGroupY = - view.parent.contentHeight \* 0.5 view.\_fixedGroup.y = viewFixedGroupY -- calculate the limits. Fixes placement errors for the scrollbar. setLimits( M, view ) -- set the widget y coord according to the calculated limits if not M.\_didSetLimits then view.y = M.bottomLimit M.\_didSetLimits = true end if not view.autoHideScrollBar then M.scrollBar:show() end return M.scrollBar end return M
I found out the default “snapBack” functionality was annoying when moving diagonally
If the limit of the scrollView was reached (or even exceeded) it would transition to the limit location. But that would stop also the momentum scroll on the vertical axis.
I changed the code so when it reaches a limit, only that axis will stop scrolling.
Feel free to use it
-- Copyright © 2013 Corona Labs Inc. All Rights Reserved. -- -- Redistribution and use in source and binary forms, with or without -- modification, are permitted provided that the following conditions are met: -- -- \* Redistributions of source code must retain the above copyright -- notice, this list of conditions and the following disclaimer. -- \* Redistributions in binary form must reproduce the above copyright -- notice, this list of conditions and the following disclaimer in the -- documentation and/or other materials provided with the distribution. -- \* Neither the name of the company nor the names of its contributors -- may be used to endorse or promote products derived from this software -- without specific prior written permission. -- \* Redistributions in any form whatsoever must retain the following -- acknowledgment visually in the program (e.g. the credits of the program): -- 'This product includes software developed by Corona Labs Inc. (http://www.coronalabs.com).' -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -- DISCLAIMED. IN NO EVENT SHALL CORONA LABS INC. BE LIABLE FOR ANY -- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. local M = {} -- Localize math functions local mAbs = math.abs local mFloor = math.floor -- configuration variables M.scrollStopThreshold = 250 -- direction variable that has a non-nil value only as long as the scrollview is scrolled M.\_direction = nil -- variable that establishes if the view limits were set ( they have to be called one time to position the scrollview correctly ) M.\_didSetLimits = false local isGraphicsV1 = ( 1 == display.getDefault( "graphicsCompatibility" ) ) -- Function to set the view's limits local function setLimits( self, view ) -- Set the bottom limit local bottomLimit = view.\_topPadding if isGraphicsV1 then bottomLimit = bottomLimit - view.\_height \* 0.5 end self.bottomLimit = bottomLimit -- TODO: use local functions for the limits instead of ifs -- Set the upper limit if view.\_scrollHeight then local upperLimit = ( -view.\_scrollHeight + view.\_height ) - view.\_bottomPadding -- the lower limit calculation is not necessary. We shift the view up with half its height, so the only thing we need to calculate -- is the upper limit. --if isGraphicsV1 then -- upperLimit = upperLimit - view.\_height \* 0.5 --end self.upperLimit = upperLimit end -- Set the right limit local rightLimit = view.\_leftPadding if isGraphicsV1 then rightLimit = rightLimit - view.\_width \* 0.5 end self.rightLimit = rightLimit -- Set the left limit if view.\_scrollWidth then local leftLimit = ( - view.\_scrollWidth + view.\_width ) - view.\_rightPadding if isGraphicsV1 then leftLimit = leftLimit - view.\_width \* 0.5 end self.leftLimit = leftLimit end end -- Function to handle vertical "snap back" on the view local function handleSnapBackVertical( self, view, snapBack ) -- Set the limits now setLimits( M, view ) local limitHit = "none" local bounceTime = 400 if not view.isBounceEnabled then bounceTime = 0 end -- Snap back vertically if not view.\_isVerticalScrollingDisabled then -- Put the view back to the top if it isn't already there ( and should be ), if we're not in a picker wheel if view.y \> self.bottomLimit or ( view.\_scrollHeight \< view.parent.height and not view.\_isUsedInPickerWheel ) then -- Set the hit limit limitHit = "bottom" -- Transition the view back to it's maximum position if "boolean" == type( snapBack ) then if snapBack == true then -- Ensure the scrollBar is at the bottom of the view if view.\_scrollBar then view.\_scrollBar:setPositionTo( "top" ) end -- Put the view back to the top view.\_tween = transition.to( view, { time = bounceTime, y = self.bottomLimit, transition = easing.outQuad } ) end end -- Put the view back to the bottom if it isn't already there ( and should be ) elseif view.y \< self.upperLimit then -- Set the hit limit limitHit = "top" -- Transition the view back to it's maximum position if "boolean" == type( snapBack ) then if snapBack == true then -- Ensure the scrollBar is at the bottom of the view if view.\_scrollBar then view.\_scrollBar:setPositionTo( "bottom" ) end -- Put the view back to the bottom view.\_tween = transition.to( view, { time = bounceTime, y = self.upperLimit, transition = easing.outQuad } ) end end end end return limitHit end -- Function to handle horizontal "snap back" on the view local function handleSnapBackHorizontal( self, view, snapBack ) -- Set the limits now setLimits( M, view ) local limitHit = "none" local bounceTime = 400 if not view.isBounceEnabled then bounceTime = 0 end -- Snap back horizontally if not view.\_isHorizontalScrollingDisabled then -- Put the view back to the left if it isn't already there ( and should be ) if view.x \< self.leftLimit then -- Set the hit limit limitHit = "left" -- Transition the view back to it's maximum position if "boolean" == type( snapBack ) then if snapBack == true then view.\_tween = transition.to( view, { time = bounceTime, x = self.leftLimit, transition = easing.outQuad } ) end end -- Put the view back to the right if it isn't already there ( and should be ) elseif view.x \> self.rightLimit then -- Set the hit limit limitHit = "right" -- Transition the view back to it's maximum position if "boolean" == type( snapBack ) then if snapBack == true then view.\_tween = transition.to( view, { time = bounceTime, x = self.rightLimit, transition = easing.outQuad } ) end end end end return limitHit end -- Function to clamp velocity to the maximum value local function clampVelocity( view ) -- Throttle the velocity if it goes over the max range if view.\_velocity\_h \< -view.\_maxVelocity then view.\_velocity\_h = -view.\_maxVelocity elseif view.\_velocity\_h \> view.\_maxVelocity then view.\_velocity\_h = view.\_maxVelocity end if view.\_velocity\_v \< -view.\_maxVelocity then view.\_velocity\_v = -view.\_maxVelocity elseif view.\_velocity\_v \> view.\_maxVelocity then view.\_velocity\_v = view.\_maxVelocity end end -- Handle momentum scrolling touch function M.\_touch( view, event ) local phase = event.phase local time = event.time local limit if "began" == phase then -- Reset values view.\_startXPos = event.x view.\_startYPos = event.y view.\_prevXPos = event.x view.\_prevYPos = event.y view.\_prevX = 0 view.\_prevY = 0 view.\_delta\_h = 0 view.\_delta\_v = 0 view.\_velocity\_h = 0 view.\_velocity\_v = 0 view.\_prevTime = 0 view.\_moveDirection = nil view.\_trackVelocity = true view.\_updateRuntime\_h = false view.\_updateRuntime\_v = false -- Set the limits now setLimits( M, view ) -- Cancel any active tween on the view if view.\_tween then transition.cancel( view.\_tween ) view.\_tween = nil end -- Set focus display.getCurrentStage():setFocus( event.target, event.id ) view.\_isFocus = true elseif view.\_isFocus then if "moved" == phase then -- Set the move direction if not view.\_moveDirection then local dx = mAbs( event.x - event.xStart ) local dy = mAbs( event.y - event.yStart ) local moveThresh = 12 if dx \> moveThresh or dy \> moveThresh then -- If there is a scrollBar, show it if view.\_scrollBar then -- Show the scrollBar, only if we need to (if the content height is higher than the view's height) -- TODO: when the diagonal scrolling comes to place, we have to treat the horizontal case as well here. if view.\_scrollBar.\_viewHeight \< view.\_scrollBar.\_viewContentHeight then view.\_scrollBar:show() end end if dx \> dy then -- If horizontal scrolling is enabled if not view.\_isHorizontalScrollingDisabled then -- The move was horizontal view.\_moveDirection = "horizontal" -- Handle vertical snap back --handleSnapBackVertical( M, view, true ) end else -- If vertical scrolling is enabled if not view.\_isVerticalScrollingDisabled then -- The move was vertical view.\_moveDirection = "vertical" -- Handle horizontal snap back --handleSnapBackHorizontal( M, view, true ) end end end end -- Horizontal movement --if "horizontal" == view.\_moveDirection then -- If horizontal scrolling is enabled if not view.\_isHorizontalScrollingDisabled then view.\_delta\_h = event.x - view.\_prevXPos view.\_prevXPos = event.x -- If the view is more than the limits if view.x \< M.leftLimit or view.x \> M.rightLimit then view.x = view.x + ( view.\_delta\_h \* 0.5 ) else view.x = view.x + view.\_delta\_h if view.\_listener and view.\_widgetType == "scrollView" then local actualDirection if view.\_delta\_h \< 0 then actualDirection = "left" elseif view.\_delta\_h \> 0 then actualDirection = "right" elseif view.\_delta\_h == 0 then if view.\_prevDeltaX and view.\_prevDeltaX \< 0 then actualDirection = "left" elseif view.\_prevDeltaX and view.\_prevDeltaX \> 0 then actualDirection = "right" end end -- if the scrollview is moving, assign the actual direction to the M.\_direction variable M.\_direction = actualDirection end end view.\_prevDeltaX = view.\_delta\_h if view.isBounceEnabled == true then -- if bounce is enabled and the view is used in picker, we snap back to prevent infinite scrolling if view.\_isUsedInPickerWheel == true then limit = handleSnapBackHorizontal( M, view, true ) else -- if not used in picker, we don't need snap back so we don't lose elastic behaviour on the tableview limit = handleSnapBackHorizontal( M, view, false ) end else limit = handleSnapBackHorizontal( M, view, true ) end end -- Vertical movement --else -- If vertical scrolling is enabled if not view.\_isVerticalScrollingDisabled then view.\_delta\_v = event.y - view.\_prevYPos view.\_prevYPos = event.y -- If the view is more than the limits if view.y \< M.upperLimit or view.y \> M.bottomLimit then view.y = view.y + ( view.\_delta\_v \* 0.5 ) -- shrink the scrollbar if the view is out of bounds if view.\_scrollBar then --view.\_scrollBar.yScale = 0.1 \* - ( view.y - M.bottomLimit ) end else view.y = view.y + view.\_delta\_v if view.\_listener and view.\_widgetType == "scrollView" then local actualDirection if view.\_delta\_v \< 0 then actualDirection = "up" elseif view.\_delta\_v \> 0 then actualDirection = "down" elseif view.\_delta\_v == 0 then if view.\_prevDeltaY and view.\_prevDeltaY \< 0 then actualDirection = "up" elseif view.\_prevDeltaY and view.\_prevDeltaY \> 0 then actualDirection = "down" end end -- if the scrollview is moving, assign the actual direction to the M.\_direction variable M.\_direction = actualDirection end end view.\_prevDeltaY = view.\_delta\_v -- Handle limits -- if bounce is true, then the snapback parameter has to be true, otherwise false if view.isBounceEnabled == true then -- if bounce is enabled and the view is used in picker, we snap back to prevent infinite scrolling if view.\_isUsedInPickerWheel == true then limit = handleSnapBackVertical( M, view, true ) else -- if not used in picker, we don't need snap back so we don't lose elastic behaviour on the tableview limit = handleSnapBackVertical( M, view, false ) end else limit = handleSnapBackVertical( M, view, true ) end -- Move the scrollBar if limit ~= "top" and limit ~= "bottom" then if view.\_scrollBar then view.\_scrollBar:move() end end -- Set the time held --view.\_timeHeld = time end --end elseif "ended" == phase or "cancelled" == phase then -- Reset values view.\_lastTime = event.time view.\_trackVelocity = false view.\_updateRuntime\_h = true view.\_updateRuntime\_v = true M.\_direction = nil -- we check if the view has a scrollStopThreshold value local stopThreshold = view.scrollStopThreshold or M.scrollStopThreshold if event.time - view.\_timeHeld \> stopThreshold then view.\_velocity\_h = 0 view.\_velocity\_v = 0 end view.\_timeHeld = 0 -- when tapping fast and the view is at the limit, the velocity changes sign. This ALWAYS has to be treated. if view.\_delta\_h \> 0 and view.\_velocity\_h \< 0 then view.\_velocity\_h = - view.\_velocity\_h end if view.\_delta\_h \< 0 and view.\_velocity\_h \> 0 then view.\_velocity\_h = - view.\_velocity\_h end if view.\_delta\_v \> 0 and view.\_velocity\_v \< 0 then view.\_velocity\_v = - view.\_velocity\_v end if view.\_delta\_v \< 0 and view.\_velocity\_v \> 0 then view.\_velocity\_v = - view.\_velocity\_v end -- Remove focus display.getCurrentStage():setFocus( nil ) view.\_isFocus = nil -- If on ended the scrollview is outside of the bounds, reposition it limit = handleSnapBackVertical( M, view, true ) end end end -- Handle runtime momentum scrolling events. function M.\_runtime( view, event ) local limit -- If we are tracking runtime if view.\_updateRuntime\_h or view.\_updateRuntime\_v then local timePassed = event.time - view.\_lastTime view.\_lastTime = view.\_lastTime + timePassed -- Stop scrolling if velocity\_h is near zero if mAbs( view.\_velocity\_h ) \< 0.01 then view.\_velocity\_h = 0 -- Hide the scrollBar if view.\_scrollBar and view.autoHideScrollBar then view.\_scrollBar:hide() end end -- Stop scrolling if velocity\_v is near zero if mAbs( view.\_velocity\_v ) \< 0.01 then view.\_velocity\_v = 0 -- Hide the scrollBar if view.\_scrollBar and view.autoHideScrollBar then view.\_scrollBar:hide() end end -- Set the velocity view.\_velocity\_h = view.\_velocity\_h \* view.\_friction view.\_velocity\_v = view.\_velocity\_v \* view.\_friction -- Clamp the velocity if it goes over the max range clampVelocity( view ) -- Horizontal movement --if "horizontal" == view.\_moveDirection then -- If horizontal scrolling is enabled if not view.\_isHorizontalScrollingDisabled and view.\_updateRuntime\_h then -- Reset limit values view.\_hasHitLeftLimit = false view.\_hasHitRightLimit = false -- Move the view view.x = view.x + view.\_velocity\_h \* timePassed -- Handle limits if "horizontal" == view.\_moveDirection then limit = handleSnapBackHorizontal( M, view, true ) else limit = handleSnapBackHorizontal( M, view, true ) end -- Left if "left" == limit then -- Stop updating the runtime now view.\_updateRuntime\_h = false -- If there is a listener specified, dispatch the event if view.\_listener then -- We have hit the left limit view.\_hasHitLeftLimit = true local newEvent = { direction = "left", limitReached = true, target = view, } view.\_listener( newEvent ) end -- Right elseif "right" == limit then -- Stop updating the runtime now view.\_updateRuntime\_h = false -- If there is a listener specified, dispatch the event if view.\_listener then -- We have hit the right limit view.\_hasHitRightLimit = true local newEvent = { direction = "right", limitReached = true, target = view, } view.\_listener( newEvent ) end end end -- Vertical movement --else -- If vertical scrolling is enabled if not view.\_isVerticalScrollingDisabled and view.\_updateRuntime\_v then -- Reset limit values view.\_hasHitBottomLimit = false view.\_hasHitTopLimit = false -- Move the view view.y = view.y + view.\_velocity\_v \* timePassed -- Move the scrollBar if view.\_scrollBar then view.\_scrollBar:move() end -- Handle limits -- if we have motion, then we check for snapback. otherwise, we don't. if "vertical" == view.\_moveDirection then limit = handleSnapBackVertical( M, view, true ) else limit = handleSnapBackVertical( M, view, true ) end -- Top if "top" == limit then -- Hide the scrollBar if view.\_scrollBar and view.autoHideScrollBar then view.\_scrollBar:hide() end -- We have hit the top limit view.\_hasHitTopLimit = true -- Stop updating the runtime now view.\_updateRuntime\_v = false -- If there is a listener specified, dispatch the event if view.\_listener then local newEvent = { direction = "up", limitReached = true, phase = event.phase, target = view, } view.\_listener( newEvent ) end -- Bottom elseif "bottom" == limit then -- Hide the scrollBar if view.\_scrollBar and view.autoHideScrollBar then view.\_scrollBar:hide() end -- We have hit the bottom limit view.\_hasHitBottomLimit = true -- Stop updating the runtime now view.\_updateRuntime\_v = false -- If there is a listener specified, dispatch the event if view.\_listener then local newEvent = { direction = "down", limitReached = true, target = view, } view.\_listener( newEvent ) end end end if mAbs( view.\_velocity\_v ) \< 0.001 then view.\_updateRuntime\_v = false end if mAbs( view.\_velocity\_h ) \< 0.001 then view.\_updateRuntime\_h = false end --end end -- If we are tracking velocity if view.\_trackVelocity then -- Calculate the time passed local newTimePassed = event.time - view.\_prevTime view.\_prevTime = view.\_prevTime + newTimePassed -- Horizontal movement --if "horizontal" == view.\_moveDirection then -- If horizontal scrolling is enabled if not view.\_isHorizontalScrollingDisabled then if view.\_prevX then local possibleVelocity = ( view.x - view.\_prevX ) / newTimePassed if possibleVelocity ~= 0 then view.\_velocity\_h = possibleVelocity -- Clamp the velocity if it goes over the max range clampVelocity( view ) end end view.\_prevX = view.x end -- Vertical movement --elseif "vertical" == view.\_moveDirection then -- If vertical scrolling is enabled if not view.\_isVerticalScrollingDisabled then if view.\_prevY then local possibleVelocity = ( view.y - view.\_prevY ) / newTimePassed if possibleVelocity ~= 0 then view.\_velocity\_v = possibleVelocity -- Clamp the velocity if it goes over the max range clampVelocity( view ) end end view.\_prevY = view.y end --end end end -- Function to create a scrollBar function M.createScrollBar( view, options ) -- Require needed widget files local \_widget = require( "widget" ) local opt = {} local customOptions = options or {} -- Setup the scrollBar's width/height local parentGroup = view.parent.parent local scrollBarWidth = options.width or 5 local viewHeight = view.\_height -- The height of the windows visible area local viewContentHeight = view.\_scrollHeight -- The height of the total content height local minimumScrollBarHeight = 24 -- The minimum height the scrollbar can be -- Set the scrollbar Height local scrollBarHeight = ( viewHeight \* 100 ) / viewContentHeight -- If the calculated scrollBar height is below the minimum height, set it to it if scrollBarHeight \< minimumScrollBarHeight then scrollBarHeight = minimumScrollBarHeight end -- Grab the theme options for the scrollBar local themeOptions = \_widget.theme.scrollBar -- Get the theme sheet file and data opt.sheet = options.sheet opt.themeSheetFile = themeOptions.sheet opt.themeData = themeOptions.data opt.width = options.frameWidth or options.width or themeOptions.width opt.height = options.frameHeight or options.height or themeOptions.height -- Grab the frames opt.topFrame = options.topFrame or \_widget.\_getFrameIndex( themeOptions, themeOptions.topFrame ) opt.middleFrame = options.middleFrame or \_widget.\_getFrameIndex( themeOptions, themeOptions.middleFrame ) opt.bottomFrame = options.bottomFrame or \_widget.\_getFrameIndex( themeOptions, themeOptions.bottomFrame ) -- Create the scrollBar imageSheet local imageSheet if opt.sheet then imageSheet = opt.sheet else local themeData = require( opt.themeData ) imageSheet = graphics.newImageSheet( opt.themeSheetFile, themeData:getSheet() ) end -- The scrollBar is a display group M.scrollBar = display.newGroup() -- Create the scrollBar frames ( 3 slice ) M.topFrame = display.newImageRect( M.scrollBar, imageSheet, opt.topFrame, opt.width, opt.height ) if not isGraphicsV1 then M.topFrame.anchorX = 0.5; M.topFrame.anchorY = 0.5 end M.middleFrame = display.newImageRect( M.scrollBar, imageSheet, opt.middleFrame, opt.width, opt.height ) if not isGraphicsV1 then M.middleFrame.anchorX = 0.5; M.middleFrame.anchorY = 0.5 end M.bottomFrame = display.newImageRect( M.scrollBar, imageSheet, opt.bottomFrame, opt.width, opt.height ) if not isGraphicsV1 then M.bottomFrame.anchorX = 0.5; M.bottomFrame.anchorY = 0.5 end -- Set the middle frame's width M.middleFrame.height = scrollBarHeight - ( M.topFrame.contentHeight + M.bottomFrame.contentHeight ) -- Positioning M.middleFrame.y = M.topFrame.y + M.topFrame.contentHeight \* 0.5 + M.middleFrame.contentHeight \* 0.5 M.bottomFrame.y = M.middleFrame.y + M.middleFrame.contentHeight \* 0.5 + M.bottomFrame.contentHeight \* 0.5 -- Setup the scrollBar's properties M.scrollBar.\_viewHeight = viewHeight M.scrollBar.\_viewContentHeight = viewContentHeight M.scrollBar.alpha = 0 -- The scrollBar is invisible initally M.scrollBar.\_tween = nil -- function to recalculate the scrollbar params, based on content height change function M.scrollBar:repositionY() self.\_viewHeight = view.\_height self.\_viewContentHeight = view.\_scrollHeight -- Set the scrollbar Height local scrollBarHeight = ( self.\_viewHeight \* 100 ) / self.\_viewContentHeight -- If the calculated scrollBar height is below the minimum height, set it to it if scrollBarHeight \< minimumScrollBarHeight then scrollBarHeight = minimumScrollBarHeight end M.middleFrame.height = scrollBarHeight -- if we have topFrame and bottomFrame as non-collected objects, we use their dimensions to recalculate the position of the scrollbar if M.topFrame and M.topFrame.contentHeight and M.bottomFrame and M.bottomFrame.contentHeight then M.middleFrame.height = M.middleFrame.height - ( M.topFrame.contentHeight + M.bottomFrame.contentHeight ) -- Positioning of the middle and bottom frames according to the new scrollbar height M.middleFrame.y = M.topFrame.y + M.topFrame.contentHeight \* 0.5 + M.middleFrame.contentHeight \* 0.5 M.bottomFrame.y = M.middleFrame.y + M.middleFrame.contentHeight \* 0.5 + M.bottomFrame.contentHeight \* 0.5 end end -- Function to move the scrollBar function M.scrollBar:move() local viewY = view.y if isGraphicsV1 then viewY = viewY + view.parent.contentHeight \* 0.5 end local moveFactor = ( viewY \* 100 ) / ( self.\_viewContentHeight - self.\_viewHeight ) local moveQuantity = ( moveFactor \* ( self.\_viewHeight - self.contentHeight ) ) / 100 if viewY \< 0 then -- Only move if not over the bottom limit if mAbs( view.y ) \< ( self.\_viewContentHeight ) then self.y = view.parent.y - view.\_top - moveQuantity end end end function M.scrollBar:setPositionTo( position ) if "top" == position then self.y = view.parent.y - view.\_top elseif "bottom" == position then self.y = self.\_viewHeight - self.contentHeight end end -- Function to show the scrollBar function M.scrollBar:show() -- Cancel any previous transition if self.\_tween then transition.cancel( self.\_tween ) self.\_tween = nil end -- Set the alpha of the bar back to 1 self.alpha = 1 end -- Function to hide the scrollBar function M.scrollBar:hide() -- If there already isn't a tween in progress if not self.\_tween then self.\_tween = transition.to( self, { time = 400, alpha = 0, transition = easing.outQuad } ) end end -- Insert the scrollBar into the fixed group and position it view.\_fixedGroup:insert( M.scrollBar ) view.\_fixedGroup.x = view.\_width \* 0.5 - scrollBarWidth \* 0.5 --local viewFixedGroupY = view.parent.y - view.\_top - view.\_height \* 0.5 -- this has to be positioned at the yCoord - half the height, no matter what. local viewFixedGroupY = - view.parent.contentHeight \* 0.5 view.\_fixedGroup.y = viewFixedGroupY -- calculate the limits. Fixes placement errors for the scrollbar. setLimits( M, view ) -- set the widget y coord according to the calculated limits if not M.\_didSetLimits then view.y = M.bottomLimit M.\_didSetLimits = true end if not view.autoHideScrollBar then M.scrollBar:show() end return M.scrollBar end return M
yeah… as much as I wanted this to work for me… it did not. not sure If I have it in the right spot… but I get an error saying:
File: widget/widgetLibrary/widget_scrollview.lua
Line: 151
Attempt to call method ‘new’ (a nil value)
I think something went wrong with the require of the new widgetLibrary and not the code of the widget itself!
Could you share some code of the part where you require the new widgetLibrary?
Ok I downloaded the open source widget library and put the widgetlibrary in my resource directory. Took the widget.lua out and put in my resource directory so my call to widget would not change. Then I did an overwrite copy with your code for momentum scrolling. Got no errors, but still no diagonal scrolling. I saw somewhere in the forum that you could put the widget.lua in with your main.lua directory and the widgetLibrary directory and it would call from it and not “stock” widget library. I don’t know if its working or not though.
In my case I need to overwrite the widget.lua too, else it wont execute the custom code… Like it does at your case now.
You could first try to get the original widget library working by putting some “print()” commands to see if it executes custom code.
After that try my code, if that doesnt work let me know
I think I fixed the problem!
I extended the original widget (as they are open-source) so it supports diagonal scrolling when both horizontal and vertical scrolling are not disabled.
I’ll share the code to help others out who find this topic
First download the open-source widget library: https://github.com/coronalabs/framework-widget and put it somewhere in your project folder.
Then add this piece of code to your main.lua to override the original widget library.
local function onRequireWidgetLibrary(name) return require("widget.widgetLibrary." .. name) end package.preload.widget = onRequireWidgetLibrary package.preload.widget\_momentumScrolling = onRequireWidgetLibrary package.preload.widget\_pickerWheel = onRequireWidgetLibrary package.preload.widget\_progressView = onRequireWidgetLibrary package.preload.widget\_scrollview = onRequireWidgetLibrary package.preload.widget\_searchField = onRequireWidgetLibrary package.preload.widget\_segmentedControl = onRequireWidgetLibrary package.preload.widget\_spinner = onRequireWidgetLibrary package.preload.widget\_stepper = onRequireWidgetLibrary package.preload.widget\_slider = onRequireWidgetLibrary package.preload.widget\_switch = onRequireWidgetLibrary package.preload.widget\_tabbar = onRequireWidgetLibrary package.preload.widget\_tableview = onRequireWidgetLibrary
Also take this code and override everything in widget/widgetLibrary/widget_momentumScrolling.lua
-- Copyright © 2013 Corona Labs Inc. All Rights Reserved. -- -- Redistribution and use in source and binary forms, with or without -- modification, are permitted provided that the following conditions are met: -- -- \* Redistributions of source code must retain the above copyright -- notice, this list of conditions and the following disclaimer. -- \* Redistributions in binary form must reproduce the above copyright -- notice, this list of conditions and the following disclaimer in the -- documentation and/or other materials provided with the distribution. -- \* Neither the name of the company nor the names of its contributors -- may be used to endorse or promote products derived from this software -- without specific prior written permission. -- \* Redistributions in any form whatsoever must retain the following -- acknowledgment visually in the program (e.g. the credits of the program): -- 'This product includes software developed by Corona Labs Inc. (http://www.coronalabs.com).' -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -- DISCLAIMED. IN NO EVENT SHALL CORONA LABS INC. BE LIABLE FOR ANY -- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. local M = {} -- Localize math functions local mAbs = math.abs local mFloor = math.floor -- configuration variables M.scrollStopThreshold = 250 -- direction variable that has a non-nil value only as long as the scrollview is scrolled M.\_direction = nil -- variable that establishes if the view limits were set ( they have to be called one time to position the scrollview correctly ) M.\_didSetLimits = false local isGraphicsV1 = ( 1 == display.getDefault( "graphicsCompatibility" ) ) -- Function to set the view's limits local function setLimits( self, view ) -- Set the bottom limit local bottomLimit = view.\_topPadding if isGraphicsV1 then bottomLimit = bottomLimit - view.\_height \* 0.5 end self.bottomLimit = bottomLimit -- TODO: use local functions for the limits instead of ifs -- Set the upper limit if view.\_scrollHeight then local upperLimit = ( -view.\_scrollHeight + view.\_height ) - view.\_bottomPadding -- the lower limit calculation is not necessary. We shift the view up with half its height, so the only thing we need to calculate -- is the upper limit. --if isGraphicsV1 then -- upperLimit = upperLimit - view.\_height \* 0.5 --end self.upperLimit = upperLimit end -- Set the right limit local rightLimit = view.\_leftPadding if isGraphicsV1 then rightLimit = rightLimit - view.\_width \* 0.5 end self.rightLimit = rightLimit -- Set the left limit if view.\_scrollWidth then local leftLimit = ( - view.\_scrollWidth + view.\_width ) - view.\_rightPadding if isGraphicsV1 then leftLimit = leftLimit - view.\_width \* 0.5 end self.leftLimit = leftLimit end end -- Function to handle vertical "snap back" on the view local function handleSnapBackVertical( self, view, snapBack ) -- Set the limits now setLimits( M, view ) local limitHit = "none" local bounceTime = 400 if not view.isBounceEnabled then bounceTime = 0 end -- Snap back vertically if not view.\_isVerticalScrollingDisabled then -- Put the view back to the top if it isn't already there ( and should be ), if we're not in a picker wheel if view.y \> self.bottomLimit or ( view.\_scrollHeight \< view.parent.height and not view.\_isUsedInPickerWheel ) then -- Set the hit limit limitHit = "bottom" -- Transition the view back to it's maximum position if "boolean" == type( snapBack ) then if snapBack == true then -- Ensure the scrollBar is at the bottom of the view if view.\_scrollBar then view.\_scrollBar:setPositionTo( "top" ) end -- Put the view back to the top view.\_tween = transition.to( view, { time = bounceTime, y = self.bottomLimit, transition = easing.outQuad } ) end end -- Put the view back to the bottom if it isn't already there ( and should be ) elseif view.y \< self.upperLimit then -- Set the hit limit limitHit = "top" -- Transition the view back to it's maximum position if "boolean" == type( snapBack ) then if snapBack == true then -- Ensure the scrollBar is at the bottom of the view if view.\_scrollBar then view.\_scrollBar:setPositionTo( "bottom" ) end -- Put the view back to the bottom view.\_tween = transition.to( view, { time = bounceTime, y = self.upperLimit, transition = easing.outQuad } ) end end end end return limitHit end -- Function to handle horizontal "snap back" on the view local function handleSnapBackHorizontal( self, view, snapBack ) -- Set the limits now setLimits( M, view ) local limitHit = "none" local bounceTime = 400 if not view.isBounceEnabled then bounceTime = 0 end -- Snap back horizontally if not view.\_isHorizontalScrollingDisabled then -- Put the view back to the left if it isn't already there ( and should be ) if view.x \< self.leftLimit then -- Set the hit limit limitHit = "left" -- Transition the view back to it's maximum position if "boolean" == type( snapBack ) then if snapBack == true then view.\_tween = transition.to( view, { time = bounceTime, x = self.leftLimit, transition = easing.outQuad } ) end end -- Put the view back to the right if it isn't already there ( and should be ) elseif view.x \> self.rightLimit then -- Set the hit limit limitHit = "right" -- Transition the view back to it's maximum position if "boolean" == type( snapBack ) then if snapBack == true then view.\_tween = transition.to( view, { time = bounceTime, x = self.rightLimit, transition = easing.outQuad } ) end end end end return limitHit end -- Function to clamp velocity to the maximum value local function clampVelocity( view ) -- Throttle the velocity if it goes over the max range if view.\_velocity\_h \< -view.\_maxVelocity then view.\_velocity\_h = -view.\_maxVelocity elseif view.\_velocity\_h \> view.\_maxVelocity then view.\_velocity\_h = view.\_maxVelocity end if view.\_velocity\_v \< -view.\_maxVelocity then view.\_velocity\_v = -view.\_maxVelocity elseif view.\_velocity\_v \> view.\_maxVelocity then view.\_velocity\_v = view.\_maxVelocity end end -- Handle momentum scrolling touch function M.\_touch( view, event ) local phase = event.phase local time = event.time local limit if "began" == phase then -- Reset values view.\_startXPos = event.x view.\_startYPos = event.y view.\_prevXPos = event.x view.\_prevYPos = event.y view.\_prevX = 0 view.\_prevY = 0 view.\_delta\_h = 0 view.\_delta\_v = 0 view.\_velocity\_h = 0 view.\_velocity\_v = 0 view.\_prevTime = 0 view.\_moveDirection = nil view.\_trackVelocity = true view.\_updateRuntime = false -- Set the limits now setLimits( M, view ) -- Cancel any active tween on the view if view.\_tween then transition.cancel( view.\_tween ) view.\_tween = nil end -- Set focus display.getCurrentStage():setFocus( event.target, event.id ) view.\_isFocus = true elseif view.\_isFocus then if "moved" == phase then -- Set the move direction if not view.\_moveDirection then local dx = mAbs( event.x - event.xStart ) local dy = mAbs( event.y - event.yStart ) local moveThresh = 12 if dx \> moveThresh or dy \> moveThresh then -- If there is a scrollBar, show it if view.\_scrollBar then -- Show the scrollBar, only if we need to (if the content height is higher than the view's height) -- TODO: when the diagonal scrolling comes to place, we have to treat the horizontal case as well here. if view.\_scrollBar.\_viewHeight \< view.\_scrollBar.\_viewContentHeight then view.\_scrollBar:show() end end if dx \> dy then -- If horizontal scrolling is enabled if not view.\_isHorizontalScrollingDisabled then -- The move was horizontal view.\_moveDirection = "horizontal" -- Handle vertical snap back handleSnapBackVertical( M, view, true ) end else -- If vertical scrolling is enabled if not view.\_isVerticalScrollingDisabled then -- The move was vertical view.\_moveDirection = "vertical" -- Handle horizontal snap back handleSnapBackHorizontal( M, view, true ) end end end end -- Horizontal movement --if "horizontal" == view.\_moveDirection then -- If horizontal scrolling is enabled if not view.\_isHorizontalScrollingDisabled then view.\_delta\_h = event.x - view.\_prevXPos view.\_prevXPos = event.x -- If the view is more than the limits if view.x \< M.leftLimit or view.x \> M.rightLimit then view.x = view.x + ( view.\_delta\_h \* 0.5 ) else view.x = view.x + view.\_delta\_h if view.\_listener and view.\_widgetType == "scrollView" then local actualDirection if view.\_delta\_h \< 0 then actualDirection = "left" elseif view.\_delta\_h \> 0 then actualDirection = "right" elseif view.\_delta\_h == 0 then if view.\_prevDeltaX and view.\_prevDeltaX \< 0 then actualDirection = "left" elseif view.\_prevDeltaX and view.\_prevDeltaX \> 0 then actualDirection = "right" end end -- if the scrollview is moving, assign the actual direction to the M.\_direction variable M.\_direction = actualDirection end end view.\_prevDeltaX = view.\_delta\_h if view.isBounceEnabled == true then -- if bounce is enabled and the view is used in picker, we snap back to prevent infinite scrolling if view.\_isUsedInPickerWheel == true then limit = handleSnapBackHorizontal( M, view, true ) else -- if not used in picker, we don't need snap back so we don't lose elastic behaviour on the tableview limit = handleSnapBackHorizontal( M, view, false ) end else limit = handleSnapBackHorizontal( M, view, true ) end end -- Vertical movement --else -- If vertical scrolling is enabled if not view.\_isVerticalScrollingDisabled then view.\_delta\_v = event.y - view.\_prevYPos view.\_prevYPos = event.y -- If the view is more than the limits if view.y \< M.upperLimit or view.y \> M.bottomLimit then view.y = view.y + ( view.\_delta\_v \* 0.5 ) -- shrink the scrollbar if the view is out of bounds if view.\_scrollBar then --view.\_scrollBar.yScale = 0.1 \* - ( view.y - M.bottomLimit ) end else view.y = view.y + view.\_delta\_v if view.\_listener and view.\_widgetType == "scrollView" then local actualDirection if view.\_delta\_v \< 0 then actualDirection = "up" elseif view.\_delta\_v \> 0 then actualDirection = "down" elseif view.\_delta\_v == 0 then if view.\_prevDeltaY and view.\_prevDeltaY \< 0 then actualDirection = "up" elseif view.\_prevDeltaY and view.\_prevDeltaY \> 0 then actualDirection = "down" end end -- if the scrollview is moving, assign the actual direction to the M.\_direction variable M.\_direction = actualDirection end end view.\_prevDeltaY = view.\_delta\_v -- Handle limits -- if bounce is true, then the snapback parameter has to be true, otherwise false if view.isBounceEnabled == true then -- if bounce is enabled and the view is used in picker, we snap back to prevent infinite scrolling if view.\_isUsedInPickerWheel == true then limit = handleSnapBackVertical( M, view, true ) else -- if not used in picker, we don't need snap back so we don't lose elastic behaviour on the tableview limit = handleSnapBackVertical( M, view, false ) end else limit = handleSnapBackVertical( M, view, true ) end -- Move the scrollBar if limit ~= "top" and limit ~= "bottom" then if view.\_scrollBar then view.\_scrollBar:move() end end -- Set the time held --view.\_timeHeld = time end --end elseif "ended" == phase or "cancelled" == phase then -- Reset values view.\_lastTime = event.time view.\_trackVelocity = false view.\_updateRuntime = true M.\_direction = nil -- we check if the view has a scrollStopThreshold value local stopThreshold = view.scrollStopThreshold or M.scrollStopThreshold if event.time - view.\_timeHeld \> stopThreshold then view.\_velocity\_h = 0 view.\_velocity\_v = 0 end view.\_timeHeld = 0 -- when tapping fast and the view is at the limit, the velocity changes sign. This ALWAYS has to be treated. if view.\_delta\_h \> 0 and view.\_velocity\_h \< 0 then view.\_velocity\_h = - view.\_velocity\_h end if view.\_delta\_h \< 0 and view.\_velocity\_h \> 0 then view.\_velocity\_h = - view.\_velocity\_h end if view.\_delta\_v \> 0 and view.\_velocity\_v \< 0 then view.\_velocity\_v = - view.\_velocity\_v end if view.\_delta\_v \< 0 and view.\_velocity\_v \> 0 then view.\_velocity\_v = - view.\_velocity\_v end -- Remove focus display.getCurrentStage():setFocus( nil ) view.\_isFocus = nil -- If on ended the scrollview is outside of the bounds, reposition it limit = handleSnapBackVertical( M, view, true ) end end end -- Handle runtime momentum scrolling events. function M.\_runtime( view, event ) local limit -- If we are tracking runtime if view.\_updateRuntime then local timePassed = event.time - view.\_lastTime view.\_lastTime = view.\_lastTime + timePassed -- Stop scrolling if velocity\_h is near zero if mAbs( view.\_velocity\_h ) \< 0.01 then view.\_velocity\_h = 0 -- Hide the scrollBar if view.\_scrollBar and view.autoHideScrollBar then view.\_scrollBar:hide() end end -- Stop scrolling if velocity\_v is near zero if mAbs( view.\_velocity\_v ) \< 0.01 then view.\_velocity\_v = 0 -- Hide the scrollBar if view.\_scrollBar and view.autoHideScrollBar then view.\_scrollBar:hide() end end if mAbs( view.\_velocity\_v ) \< 0.01 and mAbs( view.\_velocity\_h ) \< 0.01 then view.\_updateRuntime = false end -- Set the velocity view.\_velocity\_h = view.\_velocity\_h \* view.\_friction view.\_velocity\_v = view.\_velocity\_v \* view.\_friction -- Clamp the velocity if it goes over the max range clampVelocity( view ) -- Horizontal movement --if "horizontal" == view.\_moveDirection then -- If horizontal scrolling is enabled if not view.\_isHorizontalScrollingDisabled then -- Reset limit values view.\_hasHitLeftLimit = false view.\_hasHitRightLimit = false -- Move the view view.x = view.x + view.\_velocity\_h \* timePassed -- Handle limits if "horizontal" == view.\_moveDirection then limit = handleSnapBackHorizontal( M, view, true ) else limit = handleSnapBackHorizontal( M, view, true ) end -- Left if "left" == limit then -- Stop updating the runtime now view.\_updateRuntime = false -- If there is a listener specified, dispatch the event if view.\_listener then -- We have hit the left limit view.\_hasHitLeftLimit = true local newEvent = { direction = "left", limitReached = true, target = view, } view.\_listener( newEvent ) end -- Right elseif "right" == limit then -- Stop updating the runtime now view.\_updateRuntime = false -- If there is a listener specified, dispatch the event if view.\_listener then -- We have hit the right limit view.\_hasHitRightLimit = true local newEvent = { direction = "right", limitReached = true, target = view, } view.\_listener( newEvent ) end end end -- Vertical movement --else -- If vertical scrolling is enabled if not view.\_isVerticalScrollingDisabled then -- Reset limit values view.\_hasHitBottomLimit = false view.\_hasHitTopLimit = false -- Move the view view.y = view.y + view.\_velocity\_v \* timePassed -- Move the scrollBar if view.\_scrollBar then view.\_scrollBar:move() end -- Handle limits -- if we have motion, then we check for snapback. otherwise, we don't. if "vertical" == view.\_moveDirection then limit = handleSnapBackVertical( M, view, true ) else limit = handleSnapBackVertical( M, view, true ) end -- Top if "top" == limit then -- Hide the scrollBar if view.\_scrollBar and view.autoHideScrollBar then view.\_scrollBar:hide() end -- We have hit the top limit view.\_hasHitTopLimit = true -- Stop updating the runtime now view.\_updateRuntime = false -- If there is a listener specified, dispatch the event if view.\_listener then local newEvent = { direction = "up", limitReached = true, phase = event.phase, target = view, } view.\_listener( newEvent ) end -- Bottom elseif "bottom" == limit then -- Hide the scrollBar if view.\_scrollBar and view.autoHideScrollBar then view.\_scrollBar:hide() end -- We have hit the bottom limit view.\_hasHitBottomLimit = true -- Stop updating the runtime now view.\_updateRuntime = false -- If there is a listener specified, dispatch the event if view.\_listener then local newEvent = { direction = "down", limitReached = true, target = view, } view.\_listener( newEvent ) end end end --end end -- If we are tracking velocity if view.\_trackVelocity then -- Calculate the time passed local newTimePassed = event.time - view.\_prevTime view.\_prevTime = view.\_prevTime + newTimePassed -- Horizontal movement --if "horizontal" == view.\_moveDirection then -- If horizontal scrolling is enabled if not view.\_isHorizontalScrollingDisabled then if view.\_prevX then local possibleVelocity = ( view.x - view.\_prevX ) / newTimePassed if possibleVelocity ~= 0 then view.\_velocity\_h = possibleVelocity -- Clamp the velocity if it goes over the max range clampVelocity( view ) end end view.\_prevX = view.x end -- Vertical movement --elseif "vertical" == view.\_moveDirection then -- If vertical scrolling is enabled if not view.\_isVerticalScrollingDisabled then if view.\_prevY then local possibleVelocity = ( view.y - view.\_prevY ) / newTimePassed if possibleVelocity ~= 0 then view.\_velocity\_v = possibleVelocity -- Clamp the velocity if it goes over the max range clampVelocity( view ) end end view.\_prevY = view.y end --end end end -- Function to create a scrollBar function M.createScrollBar( view, options ) -- Require needed widget files local \_widget = require( "widget" ) local opt = {} local customOptions = options or {} -- Setup the scrollBar's width/height local parentGroup = view.parent.parent local scrollBarWidth = options.width or 5 local viewHeight = view.\_height -- The height of the windows visible area local viewContentHeight = view.\_scrollHeight -- The height of the total content height local minimumScrollBarHeight = 24 -- The minimum height the scrollbar can be -- Set the scrollbar Height local scrollBarHeight = ( viewHeight \* 100 ) / viewContentHeight -- If the calculated scrollBar height is below the minimum height, set it to it if scrollBarHeight \< minimumScrollBarHeight then scrollBarHeight = minimumScrollBarHeight end -- Grab the theme options for the scrollBar local themeOptions = \_widget.theme.scrollBar -- Get the theme sheet file and data opt.sheet = options.sheet opt.themeSheetFile = themeOptions.sheet opt.themeData = themeOptions.data opt.width = options.frameWidth or options.width or themeOptions.width opt.height = options.frameHeight or options.height or themeOptions.height -- Grab the frames opt.topFrame = options.topFrame or \_widget.\_getFrameIndex( themeOptions, themeOptions.topFrame ) opt.middleFrame = options.middleFrame or \_widget.\_getFrameIndex( themeOptions, themeOptions.middleFrame ) opt.bottomFrame = options.bottomFrame or \_widget.\_getFrameIndex( themeOptions, themeOptions.bottomFrame ) -- Create the scrollBar imageSheet local imageSheet if opt.sheet then imageSheet = opt.sheet else local themeData = require( opt.themeData ) imageSheet = graphics.newImageSheet( opt.themeSheetFile, themeData:getSheet() ) end -- The scrollBar is a display group M.scrollBar = display.newGroup() -- Create the scrollBar frames ( 3 slice ) M.topFrame = display.newImageRect( M.scrollBar, imageSheet, opt.topFrame, opt.width, opt.height ) if not isGraphicsV1 then M.topFrame.anchorX = 0.5; M.topFrame.anchorY = 0.5 end M.middleFrame = display.newImageRect( M.scrollBar, imageSheet, opt.middleFrame, opt.width, opt.height ) if not isGraphicsV1 then M.middleFrame.anchorX = 0.5; M.middleFrame.anchorY = 0.5 end M.bottomFrame = display.newImageRect( M.scrollBar, imageSheet, opt.bottomFrame, opt.width, opt.height ) if not isGraphicsV1 then M.bottomFrame.anchorX = 0.5; M.bottomFrame.anchorY = 0.5 end -- Set the middle frame's width M.middleFrame.height = scrollBarHeight - ( M.topFrame.contentHeight + M.bottomFrame.contentHeight ) -- Positioning M.middleFrame.y = M.topFrame.y + M.topFrame.contentHeight \* 0.5 + M.middleFrame.contentHeight \* 0.5 M.bottomFrame.y = M.middleFrame.y + M.middleFrame.contentHeight \* 0.5 + M.bottomFrame.contentHeight \* 0.5 -- Setup the scrollBar's properties M.scrollBar.\_viewHeight = viewHeight M.scrollBar.\_viewContentHeight = viewContentHeight M.scrollBar.alpha = 0 -- The scrollBar is invisible initally M.scrollBar.\_tween = nil -- function to recalculate the scrollbar params, based on content height change function M.scrollBar:repositionY() self.\_viewHeight = view.\_height self.\_viewContentHeight = view.\_scrollHeight -- Set the scrollbar Height local scrollBarHeight = ( self.\_viewHeight \* 100 ) / self.\_viewContentHeight -- If the calculated scrollBar height is below the minimum height, set it to it if scrollBarHeight \< minimumScrollBarHeight then scrollBarHeight = minimumScrollBarHeight end M.middleFrame.height = scrollBarHeight -- if we have topFrame and bottomFrame as non-collected objects, we use their dimensions to recalculate the position of the scrollbar if M.topFrame and M.topFrame.contentHeight and M.bottomFrame and M.bottomFrame.contentHeight then M.middleFrame.height = M.middleFrame.height - ( M.topFrame.contentHeight + M.bottomFrame.contentHeight ) -- Positioning of the middle and bottom frames according to the new scrollbar height M.middleFrame.y = M.topFrame.y + M.topFrame.contentHeight \* 0.5 + M.middleFrame.contentHeight \* 0.5 M.bottomFrame.y = M.middleFrame.y + M.middleFrame.contentHeight \* 0.5 + M.bottomFrame.contentHeight \* 0.5 end end -- Function to move the scrollBar function M.scrollBar:move() local viewY = view.y if isGraphicsV1 then viewY = viewY + view.parent.contentHeight \* 0.5 end local moveFactor = ( viewY \* 100 ) / ( self.\_viewContentHeight - self.\_viewHeight ) local moveQuantity = ( moveFactor \* ( self.\_viewHeight - self.contentHeight ) ) / 100 if viewY \< 0 then -- Only move if not over the bottom limit if mAbs( view.y ) \< ( self.\_viewContentHeight ) then self.y = view.parent.y - view.\_top - moveQuantity end end end function M.scrollBar:setPositionTo( position ) if "top" == position then self.y = view.parent.y - view.\_top elseif "bottom" == position then self.y = self.\_viewHeight - self.contentHeight end end -- Function to show the scrollBar function M.scrollBar:show() -- Cancel any previous transition if self.\_tween then transition.cancel( self.\_tween ) self.\_tween = nil end -- Set the alpha of the bar back to 1 self.alpha = 1 end -- Function to hide the scrollBar function M.scrollBar:hide() -- If there already isn't a tween in progress if not self.\_tween then self.\_tween = transition.to( self, { time = 400, alpha = 0, transition = easing.outQuad } ) end end -- Insert the scrollBar into the fixed group and position it view.\_fixedGroup:insert( M.scrollBar ) view.\_fixedGroup.x = view.\_width \* 0.5 - scrollBarWidth \* 0.5 --local viewFixedGroupY = view.parent.y - view.\_top - view.\_height \* 0.5 -- this has to be positioned at the yCoord - half the height, no matter what. local viewFixedGroupY = - view.parent.contentHeight \* 0.5 view.\_fixedGroup.y = viewFixedGroupY -- calculate the limits. Fixes placement errors for the scrollbar. setLimits( M, view ) -- set the widget y coord according to the calculated limits if not M.\_didSetLimits then view.y = M.bottomLimit M.\_didSetLimits = true end if not view.autoHideScrollBar then M.scrollBar:show() end return M.scrollBar end return M
I found out the default “snapBack” functionality was annoying when moving diagonally
If the limit of the scrollView was reached (or even exceeded) it would transition to the limit location. But that would stop also the momentum scroll on the vertical axis.
I changed the code so when it reaches a limit, only that axis will stop scrolling.
Feel free to use it
-- Copyright © 2013 Corona Labs Inc. All Rights Reserved. -- -- Redistribution and use in source and binary forms, with or without -- modification, are permitted provided that the following conditions are met: -- -- \* Redistributions of source code must retain the above copyright -- notice, this list of conditions and the following disclaimer. -- \* Redistributions in binary form must reproduce the above copyright -- notice, this list of conditions and the following disclaimer in the -- documentation and/or other materials provided with the distribution. -- \* Neither the name of the company nor the names of its contributors -- may be used to endorse or promote products derived from this software -- without specific prior written permission. -- \* Redistributions in any form whatsoever must retain the following -- acknowledgment visually in the program (e.g. the credits of the program): -- 'This product includes software developed by Corona Labs Inc. (http://www.coronalabs.com).' -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -- DISCLAIMED. IN NO EVENT SHALL CORONA LABS INC. BE LIABLE FOR ANY -- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. local M = {} -- Localize math functions local mAbs = math.abs local mFloor = math.floor -- configuration variables M.scrollStopThreshold = 250 -- direction variable that has a non-nil value only as long as the scrollview is scrolled M.\_direction = nil -- variable that establishes if the view limits were set ( they have to be called one time to position the scrollview correctly ) M.\_didSetLimits = false local isGraphicsV1 = ( 1 == display.getDefault( "graphicsCompatibility" ) ) -- Function to set the view's limits local function setLimits( self, view ) -- Set the bottom limit local bottomLimit = view.\_topPadding if isGraphicsV1 then bottomLimit = bottomLimit - view.\_height \* 0.5 end self.bottomLimit = bottomLimit -- TODO: use local functions for the limits instead of ifs -- Set the upper limit if view.\_scrollHeight then local upperLimit = ( -view.\_scrollHeight + view.\_height ) - view.\_bottomPadding -- the lower limit calculation is not necessary. We shift the view up with half its height, so the only thing we need to calculate -- is the upper limit. --if isGraphicsV1 then -- upperLimit = upperLimit - view.\_height \* 0.5 --end self.upperLimit = upperLimit end -- Set the right limit local rightLimit = view.\_leftPadding if isGraphicsV1 then rightLimit = rightLimit - view.\_width \* 0.5 end self.rightLimit = rightLimit -- Set the left limit if view.\_scrollWidth then local leftLimit = ( - view.\_scrollWidth + view.\_width ) - view.\_rightPadding if isGraphicsV1 then leftLimit = leftLimit - view.\_width \* 0.5 end self.leftLimit = leftLimit end end -- Function to handle vertical "snap back" on the view local function handleSnapBackVertical( self, view, snapBack ) -- Set the limits now setLimits( M, view ) local limitHit = "none" local bounceTime = 400 if not view.isBounceEnabled then bounceTime = 0 end -- Snap back vertically if not view.\_isVerticalScrollingDisabled then -- Put the view back to the top if it isn't already there ( and should be ), if we're not in a picker wheel if view.y \> self.bottomLimit or ( view.\_scrollHeight \< view.parent.height and not view.\_isUsedInPickerWheel ) then -- Set the hit limit limitHit = "bottom" -- Transition the view back to it's maximum position if "boolean" == type( snapBack ) then if snapBack == true then -- Ensure the scrollBar is at the bottom of the view if view.\_scrollBar then view.\_scrollBar:setPositionTo( "top" ) end -- Put the view back to the top view.\_tween = transition.to( view, { time = bounceTime, y = self.bottomLimit, transition = easing.outQuad } ) end end -- Put the view back to the bottom if it isn't already there ( and should be ) elseif view.y \< self.upperLimit then -- Set the hit limit limitHit = "top" -- Transition the view back to it's maximum position if "boolean" == type( snapBack ) then if snapBack == true then -- Ensure the scrollBar is at the bottom of the view if view.\_scrollBar then view.\_scrollBar:setPositionTo( "bottom" ) end -- Put the view back to the bottom view.\_tween = transition.to( view, { time = bounceTime, y = self.upperLimit, transition = easing.outQuad } ) end end end end return limitHit end -- Function to handle horizontal "snap back" on the view local function handleSnapBackHorizontal( self, view, snapBack ) -- Set the limits now setLimits( M, view ) local limitHit = "none" local bounceTime = 400 if not view.isBounceEnabled then bounceTime = 0 end -- Snap back horizontally if not view.\_isHorizontalScrollingDisabled then -- Put the view back to the left if it isn't already there ( and should be ) if view.x \< self.leftLimit then -- Set the hit limit limitHit = "left" -- Transition the view back to it's maximum position if "boolean" == type( snapBack ) then if snapBack == true then view.\_tween = transition.to( view, { time = bounceTime, x = self.leftLimit, transition = easing.outQuad } ) end end -- Put the view back to the right if it isn't already there ( and should be ) elseif view.x \> self.rightLimit then -- Set the hit limit limitHit = "right" -- Transition the view back to it's maximum position if "boolean" == type( snapBack ) then if snapBack == true then view.\_tween = transition.to( view, { time = bounceTime, x = self.rightLimit, transition = easing.outQuad } ) end end end end return limitHit end -- Function to clamp velocity to the maximum value local function clampVelocity( view ) -- Throttle the velocity if it goes over the max range if view.\_velocity\_h \< -view.\_maxVelocity then view.\_velocity\_h = -view.\_maxVelocity elseif view.\_velocity\_h \> view.\_maxVelocity then view.\_velocity\_h = view.\_maxVelocity end if view.\_velocity\_v \< -view.\_maxVelocity then view.\_velocity\_v = -view.\_maxVelocity elseif view.\_velocity\_v \> view.\_maxVelocity then view.\_velocity\_v = view.\_maxVelocity end end -- Handle momentum scrolling touch function M.\_touch( view, event ) local phase = event.phase local time = event.time local limit if "began" == phase then -- Reset values view.\_startXPos = event.x view.\_startYPos = event.y view.\_prevXPos = event.x view.\_prevYPos = event.y view.\_prevX = 0 view.\_prevY = 0 view.\_delta\_h = 0 view.\_delta\_v = 0 view.\_velocity\_h = 0 view.\_velocity\_v = 0 view.\_prevTime = 0 view.\_moveDirection = nil view.\_trackVelocity = true view.\_updateRuntime\_h = false view.\_updateRuntime\_v = false -- Set the limits now setLimits( M, view ) -- Cancel any active tween on the view if view.\_tween then transition.cancel( view.\_tween ) view.\_tween = nil end -- Set focus display.getCurrentStage():setFocus( event.target, event.id ) view.\_isFocus = true elseif view.\_isFocus then if "moved" == phase then -- Set the move direction if not view.\_moveDirection then local dx = mAbs( event.x - event.xStart ) local dy = mAbs( event.y - event.yStart ) local moveThresh = 12 if dx \> moveThresh or dy \> moveThresh then -- If there is a scrollBar, show it if view.\_scrollBar then -- Show the scrollBar, only if we need to (if the content height is higher than the view's height) -- TODO: when the diagonal scrolling comes to place, we have to treat the horizontal case as well here. if view.\_scrollBar.\_viewHeight \< view.\_scrollBar.\_viewContentHeight then view.\_scrollBar:show() end end if dx \> dy then -- If horizontal scrolling is enabled if not view.\_isHorizontalScrollingDisabled then -- The move was horizontal view.\_moveDirection = "horizontal" -- Handle vertical snap back --handleSnapBackVertical( M, view, true ) end else -- If vertical scrolling is enabled if not view.\_isVerticalScrollingDisabled then -- The move was vertical view.\_moveDirection = "vertical" -- Handle horizontal snap back --handleSnapBackHorizontal( M, view, true ) end end end end -- Horizontal movement --if "horizontal" == view.\_moveDirection then -- If horizontal scrolling is enabled if not view.\_isHorizontalScrollingDisabled then view.\_delta\_h = event.x - view.\_prevXPos view.\_prevXPos = event.x -- If the view is more than the limits if view.x \< M.leftLimit or view.x \> M.rightLimit then view.x = view.x + ( view.\_delta\_h \* 0.5 ) else view.x = view.x + view.\_delta\_h if view.\_listener and view.\_widgetType == "scrollView" then local actualDirection if view.\_delta\_h \< 0 then actualDirection = "left" elseif view.\_delta\_h \> 0 then actualDirection = "right" elseif view.\_delta\_h == 0 then if view.\_prevDeltaX and view.\_prevDeltaX \< 0 then actualDirection = "left" elseif view.\_prevDeltaX and view.\_prevDeltaX \> 0 then actualDirection = "right" end end -- if the scrollview is moving, assign the actual direction to the M.\_direction variable M.\_direction = actualDirection end end view.\_prevDeltaX = view.\_delta\_h if view.isBounceEnabled == true then -- if bounce is enabled and the view is used in picker, we snap back to prevent infinite scrolling if view.\_isUsedInPickerWheel == true then limit = handleSnapBackHorizontal( M, view, true ) else -- if not used in picker, we don't need snap back so we don't lose elastic behaviour on the tableview limit = handleSnapBackHorizontal( M, view, false ) end else limit = handleSnapBackHorizontal( M, view, true ) end end -- Vertical movement --else -- If vertical scrolling is enabled if not view.\_isVerticalScrollingDisabled then view.\_delta\_v = event.y - view.\_prevYPos view.\_prevYPos = event.y -- If the view is more than the limits if view.y \< M.upperLimit or view.y \> M.bottomLimit then view.y = view.y + ( view.\_delta\_v \* 0.5 ) -- shrink the scrollbar if the view is out of bounds if view.\_scrollBar then --view.\_scrollBar.yScale = 0.1 \* - ( view.y - M.bottomLimit ) end else view.y = view.y + view.\_delta\_v if view.\_listener and view.\_widgetType == "scrollView" then local actualDirection if view.\_delta\_v \< 0 then actualDirection = "up" elseif view.\_delta\_v \> 0 then actualDirection = "down" elseif view.\_delta\_v == 0 then if view.\_prevDeltaY and view.\_prevDeltaY \< 0 then actualDirection = "up" elseif view.\_prevDeltaY and view.\_prevDeltaY \> 0 then actualDirection = "down" end end -- if the scrollview is moving, assign the actual direction to the M.\_direction variable M.\_direction = actualDirection end end view.\_prevDeltaY = view.\_delta\_v -- Handle limits -- if bounce is true, then the snapback parameter has to be true, otherwise false if view.isBounceEnabled == true then -- if bounce is enabled and the view is used in picker, we snap back to prevent infinite scrolling if view.\_isUsedInPickerWheel == true then limit = handleSnapBackVertical( M, view, true ) else -- if not used in picker, we don't need snap back so we don't lose elastic behaviour on the tableview limit = handleSnapBackVertical( M, view, false ) end else limit = handleSnapBackVertical( M, view, true ) end -- Move the scrollBar if limit ~= "top" and limit ~= "bottom" then if view.\_scrollBar then view.\_scrollBar:move() end end -- Set the time held --view.\_timeHeld = time end --end elseif "ended" == phase or "cancelled" == phase then -- Reset values view.\_lastTime = event.time view.\_trackVelocity = false view.\_updateRuntime\_h = true view.\_updateRuntime\_v = true M.\_direction = nil -- we check if the view has a scrollStopThreshold value local stopThreshold = view.scrollStopThreshold or M.scrollStopThreshold if event.time - view.\_timeHeld \> stopThreshold then view.\_velocity\_h = 0 view.\_velocity\_v = 0 end view.\_timeHeld = 0 -- when tapping fast and the view is at the limit, the velocity changes sign. This ALWAYS has to be treated. if view.\_delta\_h \> 0 and view.\_velocity\_h \< 0 then view.\_velocity\_h = - view.\_velocity\_h end if view.\_delta\_h \< 0 and view.\_velocity\_h \> 0 then view.\_velocity\_h = - view.\_velocity\_h end if view.\_delta\_v \> 0 and view.\_velocity\_v \< 0 then view.\_velocity\_v = - view.\_velocity\_v end if view.\_delta\_v \< 0 and view.\_velocity\_v \> 0 then view.\_velocity\_v = - view.\_velocity\_v end -- Remove focus display.getCurrentStage():setFocus( nil ) view.\_isFocus = nil -- If on ended the scrollview is outside of the bounds, reposition it limit = handleSnapBackVertical( M, view, true ) end end end -- Handle runtime momentum scrolling events. function M.\_runtime( view, event ) local limit -- If we are tracking runtime if view.\_updateRuntime\_h or view.\_updateRuntime\_v then local timePassed = event.time - view.\_lastTime view.\_lastTime = view.\_lastTime + timePassed -- Stop scrolling if velocity\_h is near zero if mAbs( view.\_velocity\_h ) \< 0.01 then view.\_velocity\_h = 0 -- Hide the scrollBar if view.\_scrollBar and view.autoHideScrollBar then view.\_scrollBar:hide() end end -- Stop scrolling if velocity\_v is near zero if mAbs( view.\_velocity\_v ) \< 0.01 then view.\_velocity\_v = 0 -- Hide the scrollBar if view.\_scrollBar and view.autoHideScrollBar then view.\_scrollBar:hide() end end -- Set the velocity view.\_velocity\_h = view.\_velocity\_h \* view.\_friction view.\_velocity\_v = view.\_velocity\_v \* view.\_friction -- Clamp the velocity if it goes over the max range clampVelocity( view ) -- Horizontal movement --if "horizontal" == view.\_moveDirection then -- If horizontal scrolling is enabled if not view.\_isHorizontalScrollingDisabled and view.\_updateRuntime\_h then -- Reset limit values view.\_hasHitLeftLimit = false view.\_hasHitRightLimit = false -- Move the view view.x = view.x + view.\_velocity\_h \* timePassed -- Handle limits if "horizontal" == view.\_moveDirection then limit = handleSnapBackHorizontal( M, view, true ) else limit = handleSnapBackHorizontal( M, view, true ) end -- Left if "left" == limit then -- Stop updating the runtime now view.\_updateRuntime\_h = false -- If there is a listener specified, dispatch the event if view.\_listener then -- We have hit the left limit view.\_hasHitLeftLimit = true local newEvent = { direction = "left", limitReached = true, target = view, } view.\_listener( newEvent ) end -- Right elseif "right" == limit then -- Stop updating the runtime now view.\_updateRuntime\_h = false -- If there is a listener specified, dispatch the event if view.\_listener then -- We have hit the right limit view.\_hasHitRightLimit = true local newEvent = { direction = "right", limitReached = true, target = view, } view.\_listener( newEvent ) end end end -- Vertical movement --else -- If vertical scrolling is enabled if not view.\_isVerticalScrollingDisabled and view.\_updateRuntime\_v then -- Reset limit values view.\_hasHitBottomLimit = false view.\_hasHitTopLimit = false -- Move the view view.y = view.y + view.\_velocity\_v \* timePassed -- Move the scrollBar if view.\_scrollBar then view.\_scrollBar:move() end -- Handle limits -- if we have motion, then we check for snapback. otherwise, we don't. if "vertical" == view.\_moveDirection then limit = handleSnapBackVertical( M, view, true ) else limit = handleSnapBackVertical( M, view, true ) end -- Top if "top" == limit then -- Hide the scrollBar if view.\_scrollBar and view.autoHideScrollBar then view.\_scrollBar:hide() end -- We have hit the top limit view.\_hasHitTopLimit = true -- Stop updating the runtime now view.\_updateRuntime\_v = false -- If there is a listener specified, dispatch the event if view.\_listener then local newEvent = { direction = "up", limitReached = true, phase = event.phase, target = view, } view.\_listener( newEvent ) end -- Bottom elseif "bottom" == limit then -- Hide the scrollBar if view.\_scrollBar and view.autoHideScrollBar then view.\_scrollBar:hide() end -- We have hit the bottom limit view.\_hasHitBottomLimit = true -- Stop updating the runtime now view.\_updateRuntime\_v = false -- If there is a listener specified, dispatch the event if view.\_listener then local newEvent = { direction = "down", limitReached = true, target = view, } view.\_listener( newEvent ) end end end if mAbs( view.\_velocity\_v ) \< 0.001 then view.\_updateRuntime\_v = false end if mAbs( view.\_velocity\_h ) \< 0.001 then view.\_updateRuntime\_h = false end --end end -- If we are tracking velocity if view.\_trackVelocity then -- Calculate the time passed local newTimePassed = event.time - view.\_prevTime view.\_prevTime = view.\_prevTime + newTimePassed -- Horizontal movement --if "horizontal" == view.\_moveDirection then -- If horizontal scrolling is enabled if not view.\_isHorizontalScrollingDisabled then if view.\_prevX then local possibleVelocity = ( view.x - view.\_prevX ) / newTimePassed if possibleVelocity ~= 0 then view.\_velocity\_h = possibleVelocity -- Clamp the velocity if it goes over the max range clampVelocity( view ) end end view.\_prevX = view.x end -- Vertical movement --elseif "vertical" == view.\_moveDirection then -- If vertical scrolling is enabled if not view.\_isVerticalScrollingDisabled then if view.\_prevY then local possibleVelocity = ( view.y - view.\_prevY ) / newTimePassed if possibleVelocity ~= 0 then view.\_velocity\_v = possibleVelocity -- Clamp the velocity if it goes over the max range clampVelocity( view ) end end view.\_prevY = view.y end --end end end -- Function to create a scrollBar function M.createScrollBar( view, options ) -- Require needed widget files local \_widget = require( "widget" ) local opt = {} local customOptions = options or {} -- Setup the scrollBar's width/height local parentGroup = view.parent.parent local scrollBarWidth = options.width or 5 local viewHeight = view.\_height -- The height of the windows visible area local viewContentHeight = view.\_scrollHeight -- The height of the total content height local minimumScrollBarHeight = 24 -- The minimum height the scrollbar can be -- Set the scrollbar Height local scrollBarHeight = ( viewHeight \* 100 ) / viewContentHeight -- If the calculated scrollBar height is below the minimum height, set it to it if scrollBarHeight \< minimumScrollBarHeight then scrollBarHeight = minimumScrollBarHeight end -- Grab the theme options for the scrollBar local themeOptions = \_widget.theme.scrollBar -- Get the theme sheet file and data opt.sheet = options.sheet opt.themeSheetFile = themeOptions.sheet opt.themeData = themeOptions.data opt.width = options.frameWidth or options.width or themeOptions.width opt.height = options.frameHeight or options.height or themeOptions.height -- Grab the frames opt.topFrame = options.topFrame or \_widget.\_getFrameIndex( themeOptions, themeOptions.topFrame ) opt.middleFrame = options.middleFrame or \_widget.\_getFrameIndex( themeOptions, themeOptions.middleFrame ) opt.bottomFrame = options.bottomFrame or \_widget.\_getFrameIndex( themeOptions, themeOptions.bottomFrame ) -- Create the scrollBar imageSheet local imageSheet if opt.sheet then imageSheet = opt.sheet else local themeData = require( opt.themeData ) imageSheet = graphics.newImageSheet( opt.themeSheetFile, themeData:getSheet() ) end -- The scrollBar is a display group M.scrollBar = display.newGroup() -- Create the scrollBar frames ( 3 slice ) M.topFrame = display.newImageRect( M.scrollBar, imageSheet, opt.topFrame, opt.width, opt.height ) if not isGraphicsV1 then M.topFrame.anchorX = 0.5; M.topFrame.anchorY = 0.5 end M.middleFrame = display.newImageRect( M.scrollBar, imageSheet, opt.middleFrame, opt.width, opt.height ) if not isGraphicsV1 then M.middleFrame.anchorX = 0.5; M.middleFrame.anchorY = 0.5 end M.bottomFrame = display.newImageRect( M.scrollBar, imageSheet, opt.bottomFrame, opt.width, opt.height ) if not isGraphicsV1 then M.bottomFrame.anchorX = 0.5; M.bottomFrame.anchorY = 0.5 end -- Set the middle frame's width M.middleFrame.height = scrollBarHeight - ( M.topFrame.contentHeight + M.bottomFrame.contentHeight ) -- Positioning M.middleFrame.y = M.topFrame.y + M.topFrame.contentHeight \* 0.5 + M.middleFrame.contentHeight \* 0.5 M.bottomFrame.y = M.middleFrame.y + M.middleFrame.contentHeight \* 0.5 + M.bottomFrame.contentHeight \* 0.5 -- Setup the scrollBar's properties M.scrollBar.\_viewHeight = viewHeight M.scrollBar.\_viewContentHeight = viewContentHeight M.scrollBar.alpha = 0 -- The scrollBar is invisible initally M.scrollBar.\_tween = nil -- function to recalculate the scrollbar params, based on content height change function M.scrollBar:repositionY() self.\_viewHeight = view.\_height self.\_viewContentHeight = view.\_scrollHeight -- Set the scrollbar Height local scrollBarHeight = ( self.\_viewHeight \* 100 ) / self.\_viewContentHeight -- If the calculated scrollBar height is below the minimum height, set it to it if scrollBarHeight \< minimumScrollBarHeight then scrollBarHeight = minimumScrollBarHeight end M.middleFrame.height = scrollBarHeight -- if we have topFrame and bottomFrame as non-collected objects, we use their dimensions to recalculate the position of the scrollbar if M.topFrame and M.topFrame.contentHeight and M.bottomFrame and M.bottomFrame.contentHeight then M.middleFrame.height = M.middleFrame.height - ( M.topFrame.contentHeight + M.bottomFrame.contentHeight ) -- Positioning of the middle and bottom frames according to the new scrollbar height M.middleFrame.y = M.topFrame.y + M.topFrame.contentHeight \* 0.5 + M.middleFrame.contentHeight \* 0.5 M.bottomFrame.y = M.middleFrame.y + M.middleFrame.contentHeight \* 0.5 + M.bottomFrame.contentHeight \* 0.5 end end -- Function to move the scrollBar function M.scrollBar:move() local viewY = view.y if isGraphicsV1 then viewY = viewY + view.parent.contentHeight \* 0.5 end local moveFactor = ( viewY \* 100 ) / ( self.\_viewContentHeight - self.\_viewHeight ) local moveQuantity = ( moveFactor \* ( self.\_viewHeight - self.contentHeight ) ) / 100 if viewY \< 0 then -- Only move if not over the bottom limit if mAbs( view.y ) \< ( self.\_viewContentHeight ) then self.y = view.parent.y - view.\_top - moveQuantity end end end function M.scrollBar:setPositionTo( position ) if "top" == position then self.y = view.parent.y - view.\_top elseif "bottom" == position then self.y = self.\_viewHeight - self.contentHeight end end -- Function to show the scrollBar function M.scrollBar:show() -- Cancel any previous transition if self.\_tween then transition.cancel( self.\_tween ) self.\_tween = nil end -- Set the alpha of the bar back to 1 self.alpha = 1 end -- Function to hide the scrollBar function M.scrollBar:hide() -- If there already isn't a tween in progress if not self.\_tween then self.\_tween = transition.to( self, { time = 400, alpha = 0, transition = easing.outQuad } ) end end -- Insert the scrollBar into the fixed group and position it view.\_fixedGroup:insert( M.scrollBar ) view.\_fixedGroup.x = view.\_width \* 0.5 - scrollBarWidth \* 0.5 --local viewFixedGroupY = view.parent.y - view.\_top - view.\_height \* 0.5 -- this has to be positioned at the yCoord - half the height, no matter what. local viewFixedGroupY = - view.parent.contentHeight \* 0.5 view.\_fixedGroup.y = viewFixedGroupY -- calculate the limits. Fixes placement errors for the scrollbar. setLimits( M, view ) -- set the widget y coord according to the calculated limits if not M.\_didSetLimits then view.y = M.bottomLimit M.\_didSetLimits = true end if not view.autoHideScrollBar then M.scrollBar:show() end return M.scrollBar end return M
yeah… as much as I wanted this to work for me… it did not. not sure If I have it in the right spot… but I get an error saying:
File: widget/widgetLibrary/widget_scrollview.lua
Line: 151
Attempt to call method ‘new’ (a nil value)
I think something went wrong with the require of the new widgetLibrary and not the code of the widget itself!
Could you share some code of the part where you require the new widgetLibrary?
Ok I downloaded the open source widget library and put the widgetlibrary in my resource directory. Took the widget.lua out and put in my resource directory so my call to widget would not change. Then I did an overwrite copy with your code for momentum scrolling. Got no errors, but still no diagonal scrolling. I saw somewhere in the forum that you could put the widget.lua in with your main.lua directory and the widgetLibrary directory and it would call from it and not “stock” widget library. I don’t know if its working or not though.
In my case I need to overwrite the widget.lua too, else it wont execute the custom code… Like it does at your case now.
You could first try to get the original widget library working by putting some “print()” commands to see if it executes custom code.
After that try my code, if that doesnt work let me know