Tutorials, Videos

Swipe on mobile items

23 Comments 02 May 2011

Swipe on mobile items

I’ve been playing with the mobile item renderers this week for a customer. I wanted to reproduce a classic mobile gesture: a swipe from the right to the left on a list item to display custom actions. Extending mobile item renderers is very fun with Flex 4.5. That said, to exchange data between the list and your renderers, you need to implement an event-based micro-architecture. With Flex 4.5 and the new “view navigator” paradigm, it’s very easy. I opted for a global event dispatcher. First, let me show you the final result on my Android devices and on my iOS devices.

Here is a picture that summarizes the architecture of this Flex 4.5 application:


<ViewNavigatorApplication>

My main application uses the new ViewNavigatorApplication architecture. It automates the transitions between the view,persisting your data, navigation, actionBar components, etc… More information here: http://www.adobe.com/devnet/flex/articles/mobile-development-flex-flashbuilder.html

Instead of pushing the first view automatically, I launch a HTTPRequest to get the list of employees (an XML file stored on my server). By the way, I’m using this URL: http://www.riagora.com/sfdc/employees.xml, feel free to use it for your own demos. When I get a ResultEvent, I push the first view passing the event.result to feed the data object.

protected function employeeService_resultHandler(event:ResultEvent):void
			{
				navigator.pushView(views.sfdcempHomeView, event.result as ArrayCollection);
			}

I also declared an EventDispatcher event object. This will become my events hub for the whole application. I need it to dispatch events between the item renderers and the list of my first view.

dispatcher = new EventDispatcher;

<Views> and events

My first view displays a list of employees. The dataProvider for my <s:List> is just binded to the {data} object as it contains the ArrayCollection pushed by my main application. The interesting code is within the custom item renderer for my list.

Here the source code of my custom item renderer:

<?xml version="1.0" encoding="utf-8"?>
<s:IconItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"  creationComplete="iconitemrenderer1_creationCompleteHandler(event)"
					xmlns:s="library://ns.adobe.com/flex/spark"   height="100"  labelField="firstName" messageField="title" iconField="picture" iconWidth="64" iconHeight="64" xmlns:views="views.*" >
	<fx:Script>
		<![CDATA[
			import events.ScrollingEvent;
 
			import mx.events.EffectEvent;
			import mx.events.FlexEvent;
			import mx.events.MoveEvent;
 
			import spark.components.Button;
			import spark.components.List;
			import spark.events.ListEvent;
 
			private var FLAGSTATE:int = 2;
 
			protected function iconitemrenderer1_creationCompleteHandler(event:FlexEvent):void
			{
 
				Multitouch.inputMode = MultitouchInputMode.GESTURE;
				this.addEventListener(TransformGestureEvent.GESTURE_SWIPE, onSwipe);
 
				this.parentApplication.dispatcher.addEventListener(ScrollingEvent.SCROLLING_STARTED, onScrollAgain);
				wipeEffect.addEventListener(EffectEvent.EFFECT_END, onEffectEnd);
				wipeEffectOut.addEventListener(EffectEvent.EFFECT_END, onEffetEndOut);
			}
 
			protected function onSwipe(event:TransformGestureEvent):void
			{
 
				var myScrollEvent:ScrollingEvent = new ScrollingEvent(ScrollingEvent.SCROLLING_STARTED);
 
				if((event.offsetX == -1) && (FLAGSTATE == 2)){
 
					this.addChild(actBar);
 
					actBar.width = this.width;
					actBar.height = this.height;
					actBar.visible = true;
					actBar.theData = data;
					actBar.addEventListener(FlexEvent.CREATION_COMPLETE, onItemComplete);
					FLAGSTATE = 0;
 
					this.parentApplication.dispatcher.dispatchEvent(myScrollEvent);
				}else{
					if ((event.offsetX == -1)){
						wipeEffect.play();
						this.parentApplication.dispatcher.dispatchEvent(myScrollEvent);
					}
				}
			}
 
			protected function onItemComplete(event:FlexEvent):void
			{
				wipeEffect.play();
			}
 
			private function onScrollAgain(event:ScrollingEvent):void
			{
 
				trace("scrolling");
				if (FLAGSTATE == 1){
					wipeEffectOut.play();
				}
 
			}			
 
			protected function onEffectEnd(event:EffectEvent):void
			{
				FLAGSTATE = 1;
			}
 
			protected function onEffetEndOut(event:EffectEvent):void
			{
				FLAGSTATE = 0;
			}
 
		]]>
	</fx:Script>
	<fx:Declarations>
		<s:HGroup id="actBar2">
			<s:Button id="btn1" label="action1"/>
			<s:Button label="action2"/>
		</s:HGroup>
		<s:Parallel id="wipeEffect" target="{actBar}">
			<!--<s:Fade duration="800" alphaFrom="0.7" alphaTo="1" />-->
			<s:Move duration="300" xFrom="{this.width}" xTo="0"/>
		</s:Parallel>
		<s:Parallel id="wipeEffectOut" target="{actBar}">
			<!--<s:Fade duration="800" alphaFrom="0.7" alphaTo="1" />-->
			<s:Move duration="150" xTo="{this.width}" xFrom="0"/>
		</s:Parallel>
		<views:ActionBG id="actBar" width="{this.width}" height="{this.height}"/>
	</fx:Declarations>
</s:IconItemRenderer>

First, I add event listeners to catch “swipe” gestures events. The goal is to display a ‘toolbar’ with four potential actions (four buttons). If the user swipes on the item, I check the gesture direction (if event.offsetX == -1, then the swipe was the right to the left) and check if the toolbar is already displayed. If not, I instantiate a new ActionBG object (a custom component) and display it over the item renderer with a Move effect. For usability reasons, I had to pay attention to the duration of these Move effects and use a flag variable (FLAGSTATE) to describe the current state of my toolbar.

My item renderer code just contains functions to control the visual states. My actionBG custom component (the toolbar) displays four action buttons. Let’s look at the code of this component.

<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
		 xmlns:s="library://ns.adobe.com/flex/spark" width="400" height="150">
 
	<fx:Script>
		<![CDATA[
			import events.ScrollingEvent;
 
			import mx.events.FlexEvent;
 
			import valueObjects.Employee;
 
			public var theData:Object;
 
			protected function button1_clickHandler(event:MouseEvent):void
			{
				// DISPLAY the full name of the employee
				myTI.text = theData.id + ": " + theData.firstName + " " + theData.lastName;
			}
 
			protected function button2_clickHandler(event:MouseEvent):void
			{
				// TAP TO PUSH A NEW VIEW WITH Employee's details
				var tapEvent:ScrollingEvent = new ScrollingEvent(ScrollingEvent.TAP_ACTION);
				tapEvent.userObj = theData as Employee;
				this.parentApplication.dispatcher.dispatchEvent(tapEvent);
			}
 
			protected function button3_clickHandler(event:MouseEvent):void
			{
				// Remove the employee from the list
				var removeEvent:ScrollingEvent = new ScrollingEvent(ScrollingEvent.DELETE_ACTION);
				removeEvent.userId = int(theData.id);
				this.parentApplication.dispatcher.dispatchEvent(removeEvent);
			}
 
		]]>
	</fx:Script>
 
	<fx:Declarations>
		<!-- Place non-visual elements (e.g., services, value objects) here -->
		<!--<s:DropShadowFilter inner="true" blurY="120" alpha="0.2" angle="90" distance="20" id="innerS"/>	-->
	</fx:Declarations>
	<s:BitmapImage width="100%" height="100%" source="@Embed('assets/pattern.png')" fillMode="repeat" />
	<s:HGroup verticalAlign="middle" horizontalAlign="center" width="100%" height="100%">
		<s:Button icon="@Embed('assets/icon1.png')" click="button1_clickHandler(event)"/>
		<s:Button icon="@Embed('assets/icon3.png')" click="button2_clickHandler(event)"/>
		<s:Button icon="@Embed('assets/icon2.png')"/>
		<s:Button icon="@Embed('assets/icon4.png')" click="button3_clickHandler(event)"/>
 
	</s:HGroup>
	<s:Label id="myTI" text="" bottom="3" horizontalCenter="0" backgroundColor="0x444444" color="0xFFFFFF"/>
</s:Group>

This component has a public variable named “theData”. When I instantiate the component, I can pass some data relative to the employee. If the user clicks on the second button, the application must push a new view to display some details about the selected employee. To inform my view, I use the dispatcher object created in the main application document. To reach it, I’m simply using the parentApplication property. I also created a custom event named ScrollingEvent. I use this technique for the second button and also the last one, which is used to delete an item from the list (look at the button2 and button3 click handler functions in the code).

In my view, I just have to reuse the Dispatcher object and listen for these custom events.

protected function myList_creationCompleteHandler(event:FlexEvent):void
			{
				// TODO Auto-generated method stub
 
				this.parentApplication.dispatcher.addEventListener(ScrollingEvent.TAP_ACTION, onTapItem);
				this.parentApplication.dispatcher.addEventListener(ScrollingEvent.DELETE_ACTION, onDeleteAction);
			}
 
			private function onTapItem(event:ScrollingEvent):void
			{
				// TODO Auto Generated method stub
				this.parentApplication.dispatcher.removeEventListener(ScrollingEvent.TAP_ACTION, onTapItem);
				this.parentApplication.dispatcher.removeEventListener(ScrollingEvent.DELETE_ACTION, onDeleteAction);
				navigator.pushView(views.EmployeeDetailsView, event.userObj);
			}
 
			private function getItemIndexByProperty(array:ArrayCollection, property:String, value:String):Number
			{
 
				for (var i:Number = 0; i < array.length; i++)
				{
					var obj:Object = Object(array[i])
					if (obj[property] == value)
						return i;
				}
				return -1;
			}
 
			private function onDeleteAction(event:ScrollingEvent):void
			{
				// TODO Auto Generated method stub
 
				var userIndex:int = getItemIndexByProperty(myEmployees, "id", String(event.userId));
				myEmployees.removeItemAt(userIndex);
			}

ScrollingEvent

Last usability issue: I need to remove the toolbar on an item renderer when the user scrolls the list again, or if he swipes another item renderer. To do so, I used a new event available on the List component: touchInteraction events. Again, I use my global dispatcher to inform the item renderer that it should hide the toolbar.

In my view:

protected function myList_touchInteractionStartHandler(event:TouchInteractionEvent):void
			{
				var myScrollEvent:ScrollingEvent = new ScrollingEvent(ScrollingEvent.SCROLLING_STARTED);
				this.parentApplication.dispatcher.dispatchEvent(myScrollEvent);
 
			}
 
(...)
 
<s:List width="100%"   useVirtualLayout="false" dataProvider="{myEmployees}" creationComplete="myList_creationCompleteHandler(event)" itemRenderer="views.MyIR" touchInteractionStart="myList_touchInteractionStartHandler(event)"    height="100%" id="myList"/>

In my item renderer:

this.parentApplication.dispatcher.addEventListener(ScrollingEvent.SCROLLING_STARTED, onScrollAgain);
 
(...)
 
private function onScrollAgain(event:ScrollingEvent):void
{
 
	trace("scrolling");
	if (FLAGSTATE == 1){
		wipeEffectOut.play();
	}
 
}

Source code

The Flash Builder project is available here: http://www.riagora.com/sfdc/sfdcemp.fxp.zip

In the video, I’m using the “June update” of Flex and AIR for mobile applications. That’s why it performs very well.

 

Post to Twitter

Your Comments

23 Comments so far

  1. Bill-In-Dallas says:

    Please provide a link to download the SuperHero sdk as you specified in the project properties. Thanks.

  2. Keith says:

    I second Bil-In-Dallas’ request. Please give us access to the tools. Thanks.

  3. admin says:

    @Bill. You can today use Flex 4.5 SDK with my code, it will definitely work.The SDK I’m using will be released in June. It’s an optimized version for iOS and the BlackBerry PlayBook. But the code will remain the same.

  4. Peter says:

    hello,
    can you say how it is possible to do drag-n-drop operations on a list? maybe to resort a list.
    thanks.

  5. aura_anar says:

    Great post, i have a problem, how to embed arabic font in AIR for Android?i try use TLF but its same. Can you please give me a tutorial?

    Thanks

  6. Heramb says:

    Hi … this is just what i was trying to create :-)

    I was trying to debug the application using flash builder but am not sure how to simulate a Swipe in the simulation mode ..

    kindly guide … Thanks

  7. Jeremy says:

    btw, how would one change the list item background color?

  8. Jeremy says:

    scratch the project issue. can’t believe i can’t figure out how to change the background color for the list items…should it be done through a custom renderer or through a skin?

  9. Jeremy says:

    also, thanks for the tutorial/example! it is amazing, i’m pumped for the june update!

  10. Jeremy says:

    so it won’t run on a device with air 2.6.0.1915?

  11. 0078h says:

    Thank you, it’s gonna be very usefull for a start.
    But it looks like we cannot publish from flex to iOs in FB 4.5, only in as3 projects, so how did you do it ?

    Thanks !

  12. Niki says:

    Hi,

    Is there a way to simulate this on a device simulator instead of a device as such, I have previously used Xcode, and by pressing ctrl or cmd button use can perform getures like pinch and swipe on ios simulator. Just wanted to check if we have the same provision here.

    Thanks,

    Niki

  13. Rpgccv says:

    Perfect.. really good work 😉

    But.. doesn’t work with Flex 4.5 SDK and even if i install the .apk on the mobile (Desire HD) doens’t work too!!

    The error on the SDK:
    ‘!DeviceManager.checkingDeviceList!’ has encountered a problem.
    details – An internal error has occurred.
    java.lang.NullPointerException

    The mobile error:
    Always ask to update AIR (it’s already updated)

    Thanks anyway 😉

  14. admin says:

    @Niki. You can emulate on a MAC with a MultiTouch trackpad.

  15. admin says:

    @0078h We’ll update Flash Builder within few days. Be patient.

  16. admin says:

    @Jeremy It works on a AIR 2.6 device. Just need to recompile the app with Flex 4.5

  17. Bosun says:

    This is a great tutorial I will study it. Please. How can I call a popview/pushview within a custom component.

    Thank you.

  18. admin says:

    @Bosun Just expose the navigator property of your viewNavigator as a global variable or a managed object. Then you can access it from any component/view

  19. dewm says:

    0 down vote favorite
    share [fb] share [tw]

    I have a view that displays a list of items similar to how the email program lists emails.

    I have enabled swipe to delete within each of these items which are a set height of 87.

    This works great on the 3gs, however, even though the app is targeting the iPhone 4, the swipe does not work as expected. Instead of being able to swipe on the item you want to delete on the 4 (as you can on the 3gs) you have to swipe below the item.

    It would make sense if I was targeting the 3gs and it was giving this behavior on the 4 due to the scale up. However with it targeting the 4 and having the swipe event held within the item itself it should not be doing this.

    If anyone has any ideas on how to resolve this it would be most appreciated.

    Thanks,

    Dewm

  20. Lionel says:

    Almost a year later!
    THANKS !!!


Trackbacks/Pingbacks

  1. Getting Started Resources for Flex 4.5, Flash Builder 4.5, and Flex Mobile « Flex Doc Team - May 6, 2011

    […] Adobe evangelist Michael Chaize shows how to implement the swipe on mobile list items at http://www.riagora.com/2011/05/swipe-on-mobile-items/ […]

  2. Article publié sur MediaBox: Traduction de l’article de M. Chaise intitulé « Swipe on mobile items  « Autour de flash - July 1, 2011

    […] article est une traduction de l’article « Swipe on mobile items » écrit par Mickael Chaize.  J’ai opté pour le titre « Périphérique tactile: […]

  3. Eskimo, skins your mobile apps | RIAgora - July 25, 2011

    […] of your app depending on the screen resolutions, how to adapt your app to some UI patterns or how to manage custom gestures. Jason’s blog is also a fantastic set of resources. I was also waiting for some amazing stuff […]

Share your view

Post a comment

Who am I ?

I'm Michaël CHAIZE, Adobe Flash Platform Evangelist based in Paris. I'm a big fan of Rich Internet Applications and I promote the Flash Platform in the Enterprise world.
You can follow me on twitter: http://twitter.com/mchaize

Magazine

Follow us on Facebook

© 2018 RIAgora. Powered by WordPress.

Daily Edition Theme by WooThemes - Premium WordPress Themes