Adding gesture support for your Flex apps is very easy. But it takes a little extra code to take the experience to the next level by adding a layer of physics to it. The example below is built for touch screens but will work with a mouse just as well. If you want to view it on your mobile phone then try this link: http://polygeek.com/flex/2846_GesturePhysics/_2846_GesturePhysics.html
| view source |
The code in this example is an extension of work done by James Ward in his post Flex 4 List Scrolling on Android with Flash Player 10.1
How it works
Start by listening for applicationComplete and adding listeners for the following gestures: TOUCH_BEGIN, TOUCH_MOVIE, TOUCH_END.
public function appInit():void {
_movieService = new MovieService_2846();
_tic = new Timer( 33 );
_tic.addEventListener( TimerEvent.TIMER, onTic );
if ( Multitouch.supportsTouchEvents ) {
// supports touch events
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
FlexGlobals.topLevelApplication.stage.addEventListener(
TouchEvent.TOUCH_BEGIN, onTouchBegin );
} else {
// Does NOT support touch events
FlexGlobals.topLevelApplication.stage.addEventListener(
MouseEvent.MOUSE_DOWN, onMouseDown );
FlexGlobals.topLevelApplication.stage.addEventListener(
MouseEvent.MOUSE_UP, onMouseUp );
}
}
Here are the event handlers. The _isTouching Boolean prevents the scrolling from behaving oddly when the user tries to scroll past the top or bottom of the list.
private function onTouchBegin( e:TouchEvent ):void {
// A touch event has begun so add listeners for TOUCH_MOVE and TOUCH_END.
FlexGlobals.topLevelApplication.stage.addEventListener(
TouchEvent.TOUCH_MOVE, onTouchMove );
FlexGlobals.topLevelApplication.stage.addEventListener(
TouchEvent.TOUCH_END, onTouchEnd );
// We can stop listening for TOUCH_BEGIN until this current TOUCH_BEGIN ends.
FlexGlobals.topLevelApplication.stage.removeEventListener(
TouchEvent.TOUCH_BEGIN, onTouchBegin );
// _isTouching prevents the scrolling from behaving oddly when
// the user tries to scroll past the min/max.
_isTouching = true;
_prevY = e.stageY;
_tic.stop(); // Just to make sure the Timer isn't running.
}
I’m offloading the code to determine how to move the list to a separate function so that it can be used by both touch and mouse events.
private function onTouchMove( e:TouchEvent ):void {
moving( e.stageY );
}
moving();
tempDelta measures how far the user has moved their finger since the last time the onTouchMove event fired. I’m multipling by a small factor, 1.2 in this example, to help the user scroll. This will make the list scroll just a little faster than the user swipes. This is also beneficial in the case that you are dealing with a list of Buttons. If in the process of swiping the user presses and releases on the same button it may register a button press event – which you probably don’t want. If the list moves faster than the swipe then the chances of that happening are reduced.
Instead of setting _deltaY directly I’m going through an intermediate – _tempDelta. That is to make sure we don’t set _deltaY to 0. Sometimes that happens with the touch events registering twice just as the user finishes a swipe. It also has the added benefit of letting the user swipe once and then just place their finger over the nav to keep the nav scrolling automatically.
private function moving( stageY:int ):void {
// Multiply by a small factor - 1.2 in this example - to help the user scroll.
// This will make the app scroll just a little faster than they swipe.
var tempDelta:Number = Math.round( ( _prevY - stageY ) ) * 1.2;
// The 'tempDelta' makes sure that we don't set _deltaY to 0. Sometimes that
// happens with the touch events registering twice just as the user finishes
// a swipe. It also has the added benefit of letting the user swipe once and then
// just place their finger over the nav to keep the nav scrolling automatically.
_deltaY = ( tempDelta != 0 ) ? tempDelta : _deltaY;
// Under some circumstances just barely touching, or tapping with two fingers,
// can create a very large _deltaY. This makes sure that _deltaY doesn't exceed
// a max value of ± 20 - which is arbitrary.
_deltaY = ( _deltaY > 20 ) ? 20 : _deltaY;
_deltaY = ( _deltaY < -20 ) ? -20 : _deltaY;
calculateScrollPosition();
_prevY = stageY;
}
Now that the user has ended a swipe start the timer so we can let the nav coast to a stop.
private function onTouchEnd( e:TouchEvent ):void {
// The touch event has ended so now start listening for a TOUCH_BEGIN event again.
FlexGlobals.topLevelApplication.stage.addEventListener(
TouchEvent.TOUCH_BEGIN, onTouchBegin );
// We don't need to listen for TOUCH_MOVE or TOUCH_END until a touch event begins again.
FlexGlobals.topLevelApplication.stage.removeEventListener(
TouchEvent.TOUCH_MOVE, onTouchMove );
FlexGlobals.topLevelApplication.stage.removeEventListener(
TouchEvent.TOUCH_END, onTouchEnd );
_isTouching = false;
// Now that the user has ended a swipe start the timer so we can let the nav
// coast to a stop.
_tic.start();
}
The actual moving is done by the calculateScrollPosition() function which is called when the user is in the process of swiping and when the Timer ( _tic ) is running.
You can see that the if-statement executes when the list has reached the top. I’m using the last value of _deltaY to adjust the animation that moves the entire list down and then bounces it back into position. If _deltaY is large then the entire list will bounce a lot. If it’s just barely moving then the list will move just a little bit. Same goes for the else-statement when the list has reached the bottom.
private function calculateScrollPosition():void {
var desiredScrollPosition:Number = dataGroup.verticalScrollPosition + _deltaY;
if ( desiredScrollPosition < 0 && !_isTouching ) {
// Stop coasting the nav.
desiredScrollPosition = 0;
_tic.stop();
// Use the last value of _deltaY to adjust the bounce. If _deltaY is
// large then the nav will bounce a lot. If it's just barely moving
// then the nav will move just a little bit.
bounceBottomStart.yBy = Math.abs( _deltaY ) * 2;
bounceBottom.play();
} else if ( desiredScrollPosition > dataGroup.contentHeight - dataGroup.height
&& !_isTouching ) {
desiredScrollPosition = dataGroup.contentHeight;
_tic.stop();
// See comment above.
bounceTopStart.yBy = -Math.abs( _deltaY ) * 2;
bounceTop.play();
}
dataGroup.verticalScrollPosition = desiredScrollPosition;
}
Lastly we’ll need the onTic handler which gives the list its coasting to a stop behavior. Multiply _deltaY by a factor smaller than 1 so that the nav will slow down over time. The closer the value is to 0 the faster the nav will slow down. Likewise the closer the factor is to 1 the longer it will take. Don’t make the factor larger than 1. That would be bad.
private function onTic( e:TimerEvent ):void {
// Multiply by a factor smaller than 1 so that the nav will slow down over time.
// The closer the value is to 0 the faster the nav will slow down. Likewise the
// closer the factor is to 1 the longer it will take. Don't make the factor larger
// than 1. That would be bad.
_deltaY *= 0.9;
// 0.3 is an arbitrary threshold to stop the coasting.
if( Math.abs( _deltaY ) < 0.3 ) {
_tic.stop();
}
calculateScrollPosition();
}
You may notice the scrollBounceTop.play() and scrollBounceBottom.play(). Those are animations declared in the <fx:Declarations> section. That’s what bounces the list up or down when it scrolls to either the beginning or end.
<fx:Declarations>
<!--Easing-->
<s:Bounce id="bounce" />
<s:Power id="easeInOut" easeInFraction=".5" exponent="2" />
<!--Bounce DataGroup: top-->
<s:Sequence id="bounceBottom">
<s:Move
id="bounceBottomStart"
target="{ scroller }"
duration="400"
easer="{ easeInOut }" />
<s:Move
id="bounceBottomEnd"
target="{ scroller }"
yTo="50" duration="400"
easer="{ bounce }" />
</s:Sequence>
<!--Bounce DataGroup buttom-->
<s:Sequence id="bounceTop">
<s:Move
id="bounceTopStart"
target="{ scroller }"
duration="400"
easer="{ easeInOut }"/>
<s:Move
target="{ scroller }"
yTo="50" duration="400"
easer="{ bounce }"/>
</s:Sequence>
</fx:Declarations>
thats it




Link is broken and the application on this page yet doesn't work on a desktop computer
Fabien
Thanks. All better now.
This is a great demo and works really well on my Nexus One. One thing I think it needs adding is for the list to not scroll if you are not within 45 degrees of up and down. This allows for other gestures on item renderers to not scroll the list.
Just food for thought.
sim
@Simeon Good observation about the 45-degree constraint. There are so many nuances to consider.
nice demo there. the scrolling looks nice on my droid 2! the only criticism I have is that it appears that the touches on the menu items aren't as sensitive as I'd like to see them.
@horseman Glad you like the demo and it runs well on your Droid2. I would expect that it would since that phone is pretty powerful. The menu items aren’t anything special. Their just labels and the selection is being done automatically by the DataGroup. In a real application you’d probably have something in there a little more customized.
Yeah the droid2 is really nicer than I was expecting, and that includes flash player performance. One thing i am curious about is where you might use this aside from lists. It is a neat demo, but i wonder how much sense it makes to replicate functionality that already exists natively in the device. I can maybe see it integrated into a richer media experience but phones are still a long way from being ready for a full-on desktop rich-media extravaganza regardless of whether it is delivered as a swf or a javascript library.
@horseman I built this to experiment with the navigation for the mobile version of RunPee.com. It’s useful if someone is building a Flex website for mobile apps. Native apps are great for certain things but also have many limitations. In my case I want to make the RunPee.com website as usefull as possible even though there is an Android and iPhone app for RunPee.