Adding physics to your gestures
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
If something here has proved valuable to you then feel free to drop a couple of bucks in the tip-jar.






