Tutorials, Videos

Pull down to refresh

15 Comments 23 May 2011

Pull down to refresh

[Code updated at the end of the article]

I visited a Flex developer last week who’s developing an amazing Flex 4.5 application for tablet devices. We were talking about the classic mobile gestures on lists and the “pull down to refresh” behavior came up in the discussion. If you use the Twitter client on Android and on iOS, you can refresh the list of tweets by pulling down on the list. To reproduce this classic mobile behavior, I used a few tricks. First, I needed to catch in real-time the position of the list. Depending on the vertical scroll position, I display the “pull down to refresh” message. Then playing with the data provider of the list, and the item renderer, I display a “loading” busy indicator at the top of the list. Look at this video to appreciate the experience:

The tricks

First, to get the position of the scrollbar, just use the Scroller object attached to your list: List.scroller.verticalScrollBar.value returns the position (Check the optimized UPDATE at the end of the post). If it’s negative, then you can display the “pull down to refresh” message. To get the position in real-time, I’m just listening at Mouse_MOVE events on the list (Check the optimized UPDATE at the end of the post). Then, depending on the vertical position, I switch from the “pull down to refresh” message to “Release to refresh”.

protected function list_mouseMoveHandler(event:MouseEvent):void
			{
				var vScroll:Number = list.scroller.verticalScrollBar.value;
				if(vScroll < -20){
					trace(vScroll);
					if(!loadingGroup.visible){
						loadingGroup.visible = true;
						fadeIn.play();
					}
					loadingGroup.y = vScroll*-1 - 60;
 
					if(vScroll < -90){
						trace(arrowImage.rotation);
						if(arrowImage.rotation == 0)  {
							arrowImage.rotation = 180;
						}
						loadText.text = "Release to refesh...";
 
					}else{
						if(arrowImage.rotation == 180)  {
							arrowImage.rotation = 0;
						}
						loadText.text = "Pull down to refresh";
					}
 
				}else{
					loadingGroup.visible = false;
				}
			}

If the users releases his finger to refresh the list, I just add an item to the data provider and I launch a new HTTP request.

protected function list_mouseUpHandler(event:MouseEvent):void
			{
				if(list.scroller.verticalScrollBar.value < -90){
					loadingGroup.visible = false;
					tweets.addItemAt({text:"loading..."},0);
					// Launch HTTP request
operation1('france');
				}
			}

To display a “Loading” busy indicator, I have to analyse the data passed to the item renderer. If it contains “loading…”, then I add some children to the item renderer display list. Just declare your elements between the <fx:Declarations> tags, and instanciate them when needed. Here is the code of my item renderer:

<?xml version="1.0" encoding="utf-8"?>
<s:IconItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
					xmlns:s="library://ns.adobe.com/flex/spark" dataChange="iconitemrenderer1_dataChangeHandler(event)" creationComplete="iconitemrenderer1_creationCompleteHandler(event)"  labelField="text" messageField="from_user" iconField="profile_image_url" iconWidth="48" iconHeight="48" >
	<fx:Script>
		<![CDATA[
			import mx.events.FlexEvent;
 
			private var loadAnimCreated:Boolean = false;
 
			protected function iconitemrenderer1_creationCompleteHandler(event:FlexEvent):void
			{
				if(loadAnimCreated == false){
					if(data.text == "loading..."){
						trace("PAAS");
						addChild(loadGroup);
						loadGroup.x=0;
						loadGroup.y=0;
						loadGroup.width = this.width;
						loadGroup.height = this.height;
						loadAnimCreated = true;
					}
				}else{
					loadGroup.visible = true;
				}
			}
 
			protected function iconitemrenderer1_dataChangeHandler(event:FlexEvent):void
			{
				if((loadAnimCreated == true) && (loadGroup.visible = true)){
					loadGroup.visible = false;
				}
			}
 
		]]>
	</fx:Script>
	<fx:Declarations>
		<s:Group id="loadGroup">
			<s:Rect width="100%" height="100%">
				<s:fill>
					<s:SolidColor color="0xDDDDDD"/>
				</s:fill>
			</s:Rect>
		<s:HGroup  width="100%" height="100%" contentBackgroundColor="0xFF0000" contentBackgroundAlpha="1" verticalAlign="middle" horizontalAlign="center">
			<s:BusyIndicator width="30" height="30"/>
			<s:Label id="loadanim" color="0x444444" text="Loading new tweets..." />
			</s:HGroup>
		</s:Group>
	</fx:Declarations>
</s:IconItemRenderer>

And that’s it. Now we could imagine coding the same behavior at the end of the list to load the next 20 tweets. Using the same tricks, it should take 5 minutes to add the “pull up to load more tweets” behavior.

You can download the source code of my project here: http://riagora.com/pvt_content/android/TwitterLoader.fxp

[UPDATE] Code optimization

Thanks to your comments, I optimized the code a little bit. As mentioned in the comment, try to avoid using Mouse events on Android. Instead, use Property_Change events.

protected function view1_viewActivateHandler(event:ViewNavigatorEvent):void
			{
				operation1('france');
				list.dataGroup.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, onPropertyChange);
			}

Then, use the dataGroup.verticalScrollPosition instead of Scroller’s one.

protected function onPropertyChange(event:PropertyChangeEvent):void
			{
 
				if (event.source == event.target && event.property == "verticalScrollPosition")
				{
 
					var vScroll:Number = list.dataGroup.verticalScrollPosition;
					if(vScroll < -20){
						trace(vScroll);
						if(!loadingGroup.visible){
							loadingGroup.visible = true;
							fadeIn.play();
						}
						loadingGroup.y = vScroll*-1 - 60;
 
						if(vScroll < -90){
							trace(arrowImage.rotation);
							if(arrowImage.rotation == 0)  {
								arrowImage.rotation = 180;
							}
							loadText.text = "Release to refesh...";
 
						}else{
							if(arrowImage.rotation == 180)  {
								arrowImage.rotation = 0;
							}
							loadText.text = "Pull down to refresh";
						}
 
					}else{
						loadingGroup.visible = false;
					}
				}
			}

Last trick: instead of playing with item renderers to display the loading busy indicator, you can force the list to scroll down a little bit, and add a Group on top of the list. The Loading behavior will be separated from the item renderer class which is nicer.

Thanks for your comments.

 

Post to Twitter

Your Comments

15 Comments so far

  1. Shawn says:

    Adobe really needs to start supporting higher standards for Flex on Mobile.

    The performance of that is just so far off from being anything close to acceptable imo. It’s a cool example, but is that something we really want to see in a production application?

    We need to do better, or we’re going to give AIR Mobile a bad rap, and people will specifically avoid downloading them.

    I think you guys need to stop pushing Flex for Mobiel completely, period. Face the facts, it’s just not up to snuff. Instead, put some effort into developing a rich set of ActionScript based components?

    Flex on 2.7 is better but still no where near pure AS3. How hard would it be to put together a strong set of reusable as3 components, preferably with pre-baked native skinds (Android, iOS, Playbook).

    Think of the benefits it would do to the image of AIR, if dev are using these instead of Flex.

    We don’t need everything under the sun, but a strong set of core UI Components, a fast list and a DialogManager would go a really along way.

  2. Very cool! You might want to consider using List.dataGroup.verticalScrollPosition instead of List.scroller.verticalScrollBar.value so that this approach will work with List skins that don’t have scroll bars.

    In general acting on every mouseMove event can be slow on Android (see http://bugs.adobe.com/jira/browse/SDK-29334). For better performance I would recommend listening for scroll position changes instead of mouse move events. There is an example in this thread: http://stackoverflow.com/questions/4390725/flex-4-scroller/4425091#4425091

  3. admin says:

    Thanks Steven. You’re absolutely right, thanks for the tip. I’ll update my code and the article.

  4. admin says:

    Hi Shawn. I get your point and you’re right, Flex will always add some complexity and lower the performance compared to pure AS3. That said, the teams are really working hard to optimize both the runtime and the framework. The goal is that no one could feel the difference between native and flex, and we’re very close. We cannot forget the audience of Flex developers who just need to reuse their existing code base and components. I’ve already seen a lot of AS3 components that perform very well. They have been developed by the community. Let me retrieve them and post the link.

  5. Here is my version of the full down to refresh function (french):
    http://www.flex-tutorial.fr/2011/06/03/air-mobile-reproduire-leffet-pull-down-to-refresh-sur-une-liste-flex/
    You can download the FXP at the bottom of the article
    Less little “hacks”, more states + events ;)

    Should use that PropertyChangeEvent tho, too bad.

    Fabien

  6. Arnaud says:

    Cool Tip :),

    Now just have to join it with multi UI.

    Fail on the video with the “Release to refesh…” ;)

  7. kral says:

    Thanks all friends. Very wonderful…

  8. chris says:

    awesome, definitely better than loading a huge list up front!

  9. Laurent Mennesson says:

    Salut,
    j’interviens un peu tard sur ce post, mais j’ai trouvé une solution vraiment tres satisfaisante pour gérer la bar de refresh, 3 mois apres que tu sois passé me voir chez isobar… En faites la solution pour éviter l’effet de saccade est de directement affecter la position de la liste sur la barre : y=”{-sList.dataGroup.verticalScrollPosition}” et de rendre la list bindable [Bindable]public var sList:List, du coup a chaque changement de position de la liste, la nouvelle valeur est directement réaffecté sur la position y de la bar de refresh.
    J’ai essayé comme ca et tout fonctionne parfaitement sans saccade.
    Voila

    A+

  10. admin says:

    @Laurent. Cool, bonne astuce !

  11. Viv says:

    Would this method work for what i’m planning to do.
    I’m building a mobile app. In the app, I am calling an api to fetch top 20 photos (api only allows 20 at a time) and displaying it in a tiled 2 column list/grid. When the user reaches the 10th/last row I’d like call the api again and bring up the next 20 images and so on so it seems like it’s a continuous scroll.

    Basically, instead of a pull down I’d like the list to get updated when the user reaches the end of the list, kind of like a “pull up to refresh”.

  12. Siebe Sysmans says:

    Great article.

    However, my question is, is there already an implementation of pull-down-to-refresh in the new FB4.6?

  13. admin says:

    nope… I don’t think so…


Trackbacks/Pingbacks

  1. AIR Mobile – Reproduire l'effet "Pull down to refresh" sur une liste Flex - Adobe Flex Tutorial - Tutoriaux Flex Builder, MXML, ActionScript, AS3 - June 3, 2011

    [...] Pull Down To Refresh sur riagora.com [...]

  2. @renaun posts: Designing and Skinning Mobile Applications Link Roundup - October 10, 2011

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

© 2014 RIAgora. Powered by WordPress.

Daily Edition Theme by WooThemes - Premium WordPress Themes