detecting if an element within a scrollview is about to disappear over the edge

I have some native display objects inside a scrollview, and some UI elements overlapping the scrollview for aesthetics. Obviously this creates the issue that native elements render above everything else, so when scrolling, these elements currently end up overlapping the UI bits.

To combat, I’m wanting to hide the elements when they get too close to the edge. Aesthetically this’ll be fine because they’re already rendered with transparent backgrounds on top of standard display objects so it’ll just mean they stop being editable when they’re about to go over the edge, which isn’t going to be an issue for anybody.

Seems this is trickier than I’d expected though!

I’m using a listener to catch scroll events, and within the listener I can access event phase values, but how would I access the getContentPosition() of the scrollview from within this listener?

My plan is to use the scrollviews getContentPosition() and the individual display object positions to figure out an offset and use that to determine how close the element is to the edge of the scrollview itself… there isn’t a more ‘proper’ approach I’m guessing?

Cheers.

You don’t need to. If you know where the edge of the scroll area is in content coordinates, just convert the object’s location to content coordinates and compare.

I would recommend only ever displaying native inputs if the user is specifically trying to input. If they are not (ie: have not tapped a field they want to type into yet) then show a display object version. If that needs to be a faithful recreation then fine, but you can probably get away with just a light copy.

That was my plan - figure out the screen location by taking the getContentPosition() of the scrollview and the position of the element within the view. I can’t see how to do that from within the scrollviews listener function though?

I’d use an enterFrame() and loop your native objects to do bounds checking.  Shouldn’t be too intensive unless you have thousands of them.

Also, you know the x, y of the elements relative to the scrollview so you could bounds check based on the scrollview._view.x ( or scrollview._view.y depending on orientation).

So assuming you have a list of nativeObjects this would hide them if they are about to overflow the top of the scrollview…

local function onFrame(event) for i = 1, #nativeObjects do nativeObjects[i].isVisible = nativeObjects[i].y \> mabs(scrollView.\_view.y) end end

Hmm, an enterFrame listener is an interesting approach. This does indeed seem to work as the scrollview element is now within the scope of visible variables, but I’m using composer and creating the scrollview within the scene:show method (it needs regenerating each time the scene opens) so to do it this way I’ve now added the event bind within scene:show too…

function scene:show( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then -- more code here, stripped for simplicity local scrollingContent = widget.newScrollView( { left = 0, top = contentAreaTop, width = 320, height = contentAreaHeight, horizontalScrollDisabled = true, backgroundColor = { 233 / 255, 191 / 255, 121 / 255 }, hideScrollBar = true, bottomPadding = 40, isBounceEnabled = false } ) contentGroup:insert(scrollingContent) local function scrollListener(event) -- more code here, stripped for simplicity end Runtime:addEventListener('enterFrame', scrollListener) -- more code here, stripped for simplicity end end

and as a result, when leaving the scene I’m now getting an error because the enterFrame event is trying to work with elements that suddenly stopped existing.

Do I literally just need to unbind the event within scene:destroy or am I totally doing this wrongly?

Silly me.

Moved the scrollListener() to outside of the scene:show routine and added a removeListener to scene:hide. Job done, thanks.

Whoops, nope, I spoke too soon. Moving the listener to outside of scene:show means it can no longer see scrollingContent, because that’s a local variable within scene:show…

Bummer.

Bingo. Now declaring the scrollview outside of scene:show, but replacing it within that method. It’s not global scope so I’m able to go back to using its own event listener instead of an enterFrame bind, so the result is less expensive and since it’s global scope composer is no longer getting in the way. I don’t know why I didn’t just do this in the first place =).

Definitely time for a whisky…

You don’t need to. If you know where the edge of the scroll area is in content coordinates, just convert the object’s location to content coordinates and compare.

I would recommend only ever displaying native inputs if the user is specifically trying to input. If they are not (ie: have not tapped a field they want to type into yet) then show a display object version. If that needs to be a faithful recreation then fine, but you can probably get away with just a light copy.

That was my plan - figure out the screen location by taking the getContentPosition() of the scrollview and the position of the element within the view. I can’t see how to do that from within the scrollviews listener function though?

I’d use an enterFrame() and loop your native objects to do bounds checking.  Shouldn’t be too intensive unless you have thousands of them.

Also, you know the x, y of the elements relative to the scrollview so you could bounds check based on the scrollview._view.x ( or scrollview._view.y depending on orientation).

So assuming you have a list of nativeObjects this would hide them if they are about to overflow the top of the scrollview…

local function onFrame(event) for i = 1, #nativeObjects do nativeObjects[i].isVisible = nativeObjects[i].y \> mabs(scrollView.\_view.y) end end

Hmm, an enterFrame listener is an interesting approach. This does indeed seem to work as the scrollview element is now within the scope of visible variables, but I’m using composer and creating the scrollview within the scene:show method (it needs regenerating each time the scene opens) so to do it this way I’ve now added the event bind within scene:show too…

function scene:show( event ) local sceneGroup = self.view local phase = event.phase if ( phase == "will" ) then -- more code here, stripped for simplicity local scrollingContent = widget.newScrollView( { left = 0, top = contentAreaTop, width = 320, height = contentAreaHeight, horizontalScrollDisabled = true, backgroundColor = { 233 / 255, 191 / 255, 121 / 255 }, hideScrollBar = true, bottomPadding = 40, isBounceEnabled = false } ) contentGroup:insert(scrollingContent) local function scrollListener(event) -- more code here, stripped for simplicity end Runtime:addEventListener('enterFrame', scrollListener) -- more code here, stripped for simplicity end end

and as a result, when leaving the scene I’m now getting an error because the enterFrame event is trying to work with elements that suddenly stopped existing.

Do I literally just need to unbind the event within scene:destroy or am I totally doing this wrongly?

Silly me.

Moved the scrollListener() to outside of the scene:show routine and added a removeListener to scene:hide. Job done, thanks.

Whoops, nope, I spoke too soon. Moving the listener to outside of scene:show means it can no longer see scrollingContent, because that’s a local variable within scene:show…

Bummer.

Bingo. Now declaring the scrollview outside of scene:show, but replacing it within that method. It’s not global scope so I’m able to go back to using its own event listener instead of an enterFrame bind, so the result is less expensive and since it’s global scope composer is no longer getting in the way. I don’t know why I didn’t just do this in the first place =).

Definitely time for a whisky…