I recently discovered a new feature of AIR 1.5.1 that makes AIR applications more consumer friendly in certain situations. When you have your application set to start on user login using "NativeApplication.nativeApplication.startAtLogin = true", you may want the application to start as a background process rather than show the entire application at login. In AIR 1.5.1 there is a new property of the InvokeEvent called "reason" that will report if the application was started at login or was started when the users invokes the application by launching the application.
By capturing this event and testing for InovokeReason.LOGIN, you can keep your application invisible and only show the windows docked in the system tray. If a users starts the application it will be InvokeReason.STANDARD and you can then show the application as normal. I find it annoying to have a ton of windows popping up on my screen when I restart my computer.
I usually set up an InvokeEvent when a users clicks on the system tray icon to activate the application and bring it to the forefront. I initially set WindowsApplicatoin visible="false" so the application is invisible on start. If the the InvokeEventReason is "LOGIN", I keep the application invisible, in other cases I activate the application and bring it to the forefront.
It should be noted that the InvokeEvent fires on application start up as well as from running the the application from the IDE (so you can't test it fully unless you install the application and login again). Also, because of timing of events at start up, the application is invisible even when you set it to active, so it will blink if the InvokeEventReason is STANDARD. To avoid this behavior, I use the "windowComplete" event of the WindowedApplication to add the InvokeEvent listeners. So the code might look something like the following:
<?
xml version=
"1.0" encoding=
"utf-8"?>
<mx:WindowedApplication xmlns:mx=
"http://www.adobe.com/2006/mxml" layout=
"absolute" windowComplete=
"onAppInit()" visible=
"false" >
<mx:Script>
<![CDATA[
private function onAppInit():void
{
if (NativeApplication.supportsSystemTrayIcon) { // windows
setSystemTrayProperties();
} else if(NativeApplication.supportsDockIcon) { // mac
setDockProperties();
}
}
// add click event to the system tray
private function setSystemTrayProperties():void
{
var sysTray:SystemTrayIcon = NativeApplication.nativeApplication.icon as SystemTrayIcon;
sysTray.addEventListener(MouseEvent.CLICK, onIconClick, false, 0, true);
NativeApplication.nativeApplication.addEventListener(InvokeEvent.INVOKE, onIconClick, false, 0, true);
}
// add click event to the system tray
private function setDockProperties():void
{
NativeApplication.nativeApplication.addEventListener(InvokeEvent.INVOKE, onIconClick, false, 0, true);
}
private function onIconClick(event:InvokeEvent):void
{
trace("INVOKE REASON: " + event.reason);
if(event.reason != InvokeEventReason.LOGIN) {
if (NativeApplication.supportsSystemTrayIcon) {
stage.nativeWindow.visible = true; // win
} else {
stage.nativeWindow.activate(); // mac
}
stage.nativeWindow.orderToFront();
{
}
]]>
</mx:Script>
- Mister
Just a quick post because I have seen this done on other sites but it no longer is valid in AIR 1.5. Most Mac OS X applications minimize to system tray on closing the application using the "x" close button on the app. To achieve this in AIR 1.5 you will need to do the following on creationComplete event of the main application:
// on application creation complete setup default Mac OS X behavior
private function init
():
void
{
if (NativeApplication.
supportsMenu) { // we are on a Mac
this.
nativeWindow.
addEventListener(InvokeEvent.
INVOKE, handleAppInvoke
);
this.
nativeWindow.
addEventListener(Event.
CLOSING, handleAppHide
);
NativeApplication.
nativeApplication.
addEventListener(Event.
EXITING, handleAppExit
);
NativeApplication.
nativeApplication.
autoExit =
false;
} else { // we are on a Windows machine
this.
nativeWindow.
addEventListener(Event.
CLOSING, handleAppClosing
);
NativeApplication.
nativeApplication.
autoExit =
true;
}
}
// active that application
private function handleAppInvoke(event:InvokeEvent):void
{
this.nativeWindow.activate();
}
// close all open windows
private function handleAppClosing(event:Event):void
{
event.preventDefault();
for (var i:int = NativeApplication.nativeApplication.openedWindows.length - 1; i>= 0; --i) {
NativeWindow(NativeApplication.nativeApplication.openedWindows[i]).close();
}
}
// hide the application instead of closing it
private function handleAppHide(event:Event):void
{
if( this.nativeWindow.visible ) { // if the window is visible the event behaviour is canceld and the window is hidden
event.preventDefault();
this.nativeWindow.visible = false;
}
}
// close all open windows, remove event listener for hiding application and close applicatin
private function handleAppExit(event:Event):void
{
this.nativeWindow.removeEventListener(Event.CLOSING, handleAppHide);
for (var i:int = NativeApplication.nativeApplication.openedWindows.length - 1; i>= 0; --i) {
NativeWindow(NativeApplication.nativeApplication.openedWindows[i]).close();
}
this.close();
}
- Mister
On a recent AIR project I have become aware and subsequently obsessed with memory. My concerns have centered around garbage collection and event listeners, specifically how you clean up event listeners to allow Garbage Collection (GC) to clean up objects. Most DisplayObjects implement the EventDispatcher class that allows it to broadcast events. Trolling around the googleverse I found some interesting ways to insure that you break strong references to allow clean up or manually remove event listeners on your own to make your application more memory savvy.
When you use addEventListener() method and register a event listener to an object you create reference. By default any object that has a reference to a listener will keep that reference until it is removed using the removeEventListener() method. Strong references will not be cleaned up by Flash player GC and remain in memory until the application is closed or the world ends, whichever comes first. This makes for a horribly leaky AIR applications, especially those applications that may run for days on the user's system without being restarted.
Use Additional Parameters of Listener
The addEventListener() method does have some additional (and seldom used) parameters to create a weak reference when adding the listener to an object. On of those parameters is "useWeakReference", the 5th parameter of the method. To utilize this parameter you need to set it from false to true:
addEventListener
(type:
String, listener:
Function, useCapture:
Boolean =
false, priority:
int =
0, useWeakReference:
Boolean =
false);
.....
var button:Button = new Button()
button.addEventListener(MouseEvent.click, handleMouseClick, false, 0, true);
private function handleMouseClick(event:MouseEvent):void
{
// handle the event
}
This will allow the event to be cleaned up at some point by Flash garbage collection. If you are like me and and find it annoying that the useWeakReference parameter of the addEventListener() method is false by default, you can can extend the object and make useWeakReference true by default, then use that object in your project:
package com.
custom.
controls
{
import mx.
controls.
Button;
public class CustomButton extends Button
{
public function CustomButton()
{
super();
}
override public function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=true):void
{
super.addEventListener(type, listener, useCapture, priority, useWeakReference);
}
}
}
Explicitly Removing Listeners
Even though setting useWeakReference=true will most likely mark an object for clean up, to be even more certain you should explicitly remove the event listener with the removeEventListener() method:
private var button:
Button;
private function init()
{
var button:Button = new Button();
button.addEventListener(MouseEvent.click, handleMouseClick, false, 0, <strong>true</strong>);
}
private function handleMouseClick(event:MouseEvent):void
{
button.removeEventListener(handleMouseClick);
button = new Button();
// handle the event
}
Now you are certain that you have cleaned up and removed the listener manually and the object will be removed at some point during GC since there is no longer an explicit reference to the object.
Anonymous Functions
Another technique that comes in handy is to removing event listeners when using anonymous functions as event handlers. The trick to this techniques is the arguments.callee:
var button:Button = new Button()
button.addEventListner(MouseEvent.CLICK, function(event:MouseEvent):void {
event.currentTarget.removeEventListener(event.type, arguments.callee);
});
Bummer
Now for the disappointment. If you create an event listener using MXML they are always going to have strong reference by default with no way to change useWeakReference and they make it difficult to use removeEventListener() method:
private function handleClickEvent
(event:Event
):
void
{
// handle event
}
<mx:Button click="handleClickEvent(event)"/>
So these objects will not be cleaned up when created in this way unless you have some clean up method that explicitly removes the event listeners or you add the event listeners to MXML object using ActionScript instead of binding.
So basically you need to add objects or listeners in ActionScript rather than use MXML when using the addEventListener() method, which sort of makes MXML less valuable in some respects because binding to objects and listening to events is what drives the framework.
Additional Links
For a great discussion on the topic, see Ted Patricks post and be sure to read all the comments as many of the examples were taken from discussion.
Garbage Collection Articles:
http://spreadingfunkyness.com/garbage-collection-with-flex-and-adobe-air/
http://blogs.adobe.com/aharui/2007/03/garbage_collection_and_memory.html
http://gskinner.com/blog/archives/2006/08/as3_resource_ma_2.html
http://www.adobe.com/devnet/flashplayer/articles/resource_management.html
I think the NativeMenu is a great option for AIR applications as you can get a consistent look for the application that matches the operating system. The other advantage is that NativeMenu's are "outside" of the application and do not get clipped as they would if you just created a popup Canvas with a List control. However, I could not get the native menu to appear as a popup in my tests. In fact, it would often result in crashing the AIR application when i did NativeMenu.display();.
If I call a function that creates a NativeMenu from a button click everything works great. If I call the same function to create the menu from an event (like receiving data from a service call), the menu is not created and the application crashes upon subsequent attempts to create the menu. In addition, trying to create a NativeMenu popup on "createComplete" of the application also causes a crash.
So let's say you have an application that you want to show a NativeMenu as a popup on startup of the application so you run the following:
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="init()" width="540" height="388">
<mx:Style source="styles.css"/>
<mx:Script>
<![CDATA[
private function init():void
{
var nativeMenu:NativeMenu = new NativeMenu();
var menuItem1:NativeMenuItem = new NativeMenuItem("Menu Item 1");
var menuItem2:NativeMenuItem = new NativeMenuItem("Menu Item 2");
nativeMenu.addItem(menuItem1);
nativeMenu.addItem(menuItem2);
NativeApplication.nativeApplication.menu.display(this.stage, 100, 100);
}
]]>
</mx:Script>
</mx:WindowedApplication>
This will cause an immediate crash of the application. But if you create an non-poppup NativeMenu, it works:
<?
xml version=
"1.0" encoding=
"utf-8"?>
<mx:WindowedApplication xmlns:mx=
"http://www.adobe.com/2006/mxml" layout=
"absolute" creationComplete=
"init()" width=
"540" height=
"388">
<mx:Style source=
"styles.css"/>
<mx:Script>
<!
[CDATA
[
private function init
():
void
{
var nativeMenu:NativeMenu =
new NativeMenu
();
var menuItem1:NativeMenuItem =
new NativeMenuItem
("Menu Item 1");
var menuItem2:NativeMenuItem =
new NativeMenuItem
("Menu Item 2");
nativeMenu.
addItem(menuItem1
);
nativeMenu.
addItem(menuItem2
);
if(NativeApplication.
supportsMenu){
var menuItem:NativeMenuItem =
new NativeMenuItem
("Menu Name");
menuItem.
submenu = nativeMenu;
NativeApplication.
nativeApplication.
menu.
addItem(menuItem
);
}
}
]]>
</mx:Script>
</mx:WindowedApplication>
This works just fine. Initially I assumed this is some kind of timing issue or a possible undocumented bug. After a ton of research and much wasted time, I did find a possible bug mentioned by Marco Casario in his book "The Essential Guide to Flash CS4 AIR Development book is oriented to Flash developers ". Here is a snippet from his chapter on creating NatveMenu's:
The display() method of the NativeMenu class only works properly on Microsoft Windows systems. Flash Player doesn’t work properly on Mac OS X systems, and displays the pop- up menu on the cursor of the mouse instead of at the specified coordinates. This occurs if the call to the display() function happens as a consequence of an event of the MouseEvent class . If the call happens in response to other events, the application doesn’t display the pop- up menu at all.
This limitation was not mentioned in the Adobe Documentation and as it happens, I was trying to create a NativeMenu popup for my AIR application on a Mac. I hate when you find these things out the hard way and waste a bunch of time tracking down obscure undocumented issues. If this is a limitation, it should clearly be presented as such when you look at the examples in the Adobe help. I think this is a Flash player on Mac issue, but so far I can't find any official documentation on the problem nor could I track down an actual bug report. If anyone has any suggestions, please pass them on, thanks!
-Mr
The TextArea in Flex comes with its own contextual menu, so adding one does absolutely nothing but leave you wondering what the heck you are doing wrong. In the bug report there is a bit of a work around for AIR 1.5 which requires you to access the "textField" property of the TextArea and add your contextMenu to that item. Apparently there are not enough people using custom contextMenu's to justify fixing the bug. The problem gets a bit trickier as the textField is protected in Flex Builder 3, so you can't just do:
myTextArea.textfield.contextMenu = myMenu;
You need to expose or get at the textField to add the menu. You can do this by using mx_internal (careful, kid, or you'll put your eye out with that thing!). So you can throw a little hack together to get your context menu on your text area:
private function createMenu
():
void
{
var mainMenu:
ContextMenu =
new ContextMenu();
var menuitem:
ContextMenuItem =
new ContextMenuItem("hello mom!");
menuitem.
addEventListener(ContextMenuEvent.
MENU_ITEM_SELECT, handleContextMenuEvent
);
mainMenu.
addItem(menuitem
);
textInput.contextMenu = mainMenu;
var txt:TextField = textInput.mx_internal::getTextField() as TextField;
txt.contextMenu = mainMenu;
}
private function handleContextMenuEvent(event:ContextMenuEvent):void
{
var menuitem:ContextMenuItem = event.target as ContextMenuItem;
trace("context menu event: " + menuitem);
}
<mx:TextArea id="textInput" width="100%" height="100%">
Notice that I added the contextMenu to two spots, the textField inside the TextArea, and the TextArea itself, this will show only the "hello mom!". If you wanted to just add "hello mom!" to the existing menu options of the TextArea (copy/paste/select all), then you can just add the menu to the textField only.
I thought I might be able to accomplish the same behavior using an AIR NativeMenu/NativeMenuItem. Using a NativeMenu instead of a ContextMenu item on a TextArea throws this error:
ReferenceError: Error #1069: Property clipboardItems not found on flash.display.NativeMenu and there is no default value.
at flash.display::InteractiveObject/onContextMenuEvent()
The workaround would then be to create a NativeMenu popup and position on the cursor position of the TextArea.
private function createMenu
():
void
{
var mainMenu:NativeMenu =
new NativeMenu
();
var menuitem:NativeMenuItem = new NativeMenuItem("hello mom!");
menuitem.addEventListener(Event.DISPLAYING, handleDisplaying);
mainMenu.addItem(menuitem);
mainMenu.display(NativeApplication.nativeApplication.activeWindow.stage, 20, 20);
}
private function handleDisplaying(event:Event):void
{
trace("native menu displayed");
}
-Mr
I thought I had lost my phone for the past day, I looked every where and called it a few times, no luck. So I remembered I had the InstaMapper GPS software installed. You can send an SMS to the phone with your InstaMapper key to activate the GPS and have it report its location. Worked perfectly, apparently the phone is at my house some place hanging out without me, nice. I think its pretty cool that you can activate applications this way with the G1 phone.
Since iPhones don't allow applications to run in the background, it wouldn't have been possible, just another lost phone ringing with nobody to hear its plea to be found. I recommend anyone with a G1 to use InstaMapper. I also created an AIR application that tracks your device with Google Maps.
-Mr
I ran into a problem right when we were going to launch a new Flex web-based application to production (of course) with Internet Explorer and Flex. I was calling an RESTful service that returns XML and using the built-in Flex HTTPService with either POST/GET as the method for the request. Most of the service calls either return a HTTP Status Code of 200 (OK) or they throw an error. For one POST request that returned a HTTP Status of "201 Created" Flex threw up a 2032 Stream Error in response. Here is a brief definition of the error:
The referenced resource does not exist or the swf is trying to access a file across a restricted domain.
This error causes Flex to report a fault error on the request even though it was successful. The 201 status code is handled fine in all other browsers except IE (6 & 7). I did some research on the issue thinking it must be something easy to fix, that many others have run across before me. It seems the error is out there for lots of different reasons.
One post reported that IE was having a caching issues, and the way to solve it was to add a random number on the end of your service calls or you have to set the response headers in your server side page to prevent caching. This one sounds similar to a FireFox issue I ran into a couple years back, but didn't effect the results:
http://www.judahfrangipane.com/blog/?p=87
The overwhelming number of posts on the subject suggested that Rails and Adobe both share the responsibility for this error (depending on if you are a Rails dev or a Flex dev, the debate rages as to who is to blame). The fix seems to be changing status returned from Rails to something other than 201. This seems to be the legit solution since the web service I was hitting is running Rails:
http://stackoverflow.com/questions/354936/flex-2032-stream-error-in-ie-only
http://nachbar.name/blog/2008/06/14/flex-railsprotect_from_forgery-problem-with-rails-21-produces-ioerror-2032/
http://onrails.org/articles/2008/02/20/dealing-with-http-errors-in-a-flex-with-rails-application
http://www.manning-sandbox.com/message.jspa?messageID=64372
http://nachbar.name/blog/tag/adobe-flex-programming/
If you think Adobe should fix the issue (as some Rails people protest) then you can view all the other bug reports Adobe already had lined up to fix that involve a 2032 Stream Error, just type in Explorer 2032 and you will find them:
http://bugs.adobe.com/jira/secure/QuickSearch.jspa
The workaround we ended up implemented was totally client based. In the fault handler of this particular service request, I look for the errorID value of the Fault event and ignore it. It was all I could do on short notice and I wasn't about to convince the backend dudes that we needed to change the server response from 201 to 200, pick your battles and my first battle was to get this app into the war.
For me the real culprit is IE (easy target). Something that works in every other browser but IE is so friggen familiar to anyone who has done any cross-browser work. IE sucks, it has always sucked, and it will always suck, period. My other thought is about web services and codes in general. As an application developer, I use a lot of different services, consuming data from many different pipes. I couldn't tell you as a consumer of web services and an application designer what the value of having a returned Status of 201 has over returning a Status of 200, can you? I make a request, and if the request is good, just tell me ok, I don't need to know that the server returned a HTTP Status Code of 311 (the server code for "I just made you a sandwich"). I need to know only two basic things, was the request good, or did it explode?
I think some API dudes and backend dudes are purists, they want server codes to bubble up to the client so we can admire them, even though in practice (meaning you actually build applications with these services) you don't really need the extra data. Hey, I am totally used to having to make 5 service calls to accomplish one thing with a web service, I would never ask for the service to be changed to convenience the client, but some information is superfluous and could be scaled down.
UPDATE
Saw another blog post about the same topic at UserFlex.
- Mister
This example is based on Alex Harui's Smooth Scrolling List. It just changes the code to work with the HorizontalList control rather than the List control. The SmoothHorizontalScrollingList class extends the HorizontalList control adding the needed functionality to allow smooth scrolling:
Example
Source file: smoothscrollinghorizontallist.zip
Code
package com.
custom.
controls
{
import flash.
display.
DisplayObject;
import flash.
events.
Event;
import flash.
events.
MouseEvent;
import mx.
controls.
HorizontalList;
import mx.
core.
ScrollPolicy;
import mx.
effects.
easing.*;
import mx.
events.
ScrollEvent;
import mx.
events.
ScrollEventDetail;
/**
* List that uses smooth scrolling
*/
public class SmoothHorizontalScrollingList extends HorizontalList
{
private var fudge:Number;
public function SmoothHorizontalScrollingList()
{
super();
offscreenExtraRowsOrColumns = 2;
}
override protected function configureScrollBars():void
{
super.configureScrollBars();
if (horizontalScrollBar)
horizontalScrollBar.lineScrollSize = .125; // should be inverse power of 2
}
override public function get horizontalScrollPosition():Number
{
if (!isNaN(fudge))
{
var vsp:Number = super.horizontalScrollPosition + fudge;
fudge = NaN;
return vsp;
}
return Math.floor(super.horizontalScrollPosition);
}
override protected function scrollHandler(event:Event):void
{
// going backward is trickier. When you cross from, for instance 2.1 to 1.9, you need to convince
// the superclass that it is going from 2 to 1 so the delta is -1 and not -.2.
// we do this by adding a fudge factor to the first return from horizontalScrollPosition
// which is used by the superclass logic.
var last:Number = super.horizontalScrollPosition;
var vsp:Number = this.horizontalScrollBar.scrollPosition;
if (vsp <last)
{
if (last != Math.floor(last) || vsp != Math.floor(vsp))
{
if (Math.floor(vsp) <Math.floor(last))
{
fudge = Math.floor(last) - Math.floor(horizontalScrollBar.scrollPosition);
trace(last.toFixed(2), vsp.toFixed(2), fudge);
}
}
}
super.scrollHandler(event);
var pos:Number = super.horizontalScrollPosition;
// if we get a THUMB_TRACK, then we need to calculate the position
// because it gets rounded to an int by the ScrollThumb code, and
// we want fractional values.
if (event is ScrollEvent)
{
var se:ScrollEvent = ScrollEvent(event);
if (se.detail == ScrollEventDetail.THUMB_TRACK)
{
if (horizontalScrollBar.numChildren == 4)
{
var downArrow:DisplayObject = horizontalScrollBar.getChildAt(3);
var thumb:DisplayObject = horizontalScrollBar.getChildAt(2);
pos = (thumb.y - downArrow.height) / (downArrow.y - thumb.height - downArrow.height) * this.maxHorizontalScrollPosition;
// round to nearest lineScrollSize;
pos /= horizontalScrollBar.lineScrollSize;
pos = Math.round(pos);
pos *= horizontalScrollBar.lineScrollSize;
//trace("faked", pos);
}
}
}
var fraction:Number = pos - horizontalScrollPosition;
fraction *= this.columnWidth;
//trace("was", listContent.y.toFixed(2));
listContent.move(viewMetrics.left + listContent.leftOffset - fraction, listContent.y);
//trace("now", listContent.y.toFixed(2), fraction.toFixed(2), listItems[0][0].data.lastName);
}
}
}
The following code is an example of how to use the custom control in a Flex application:
<?
xml version=
"1.0" encoding=
"utf-8"?>
<mx:Application xmlns:mx=
"http://www.adobe.com/2006/mxml" layout=
"absolute" xmlns:controls=
"com.custom.controls.*" creationComplete=
"init()" height=
"383" width=
"388">
<mx:Script>
<!
[CDATA
[
import mx.
collections.
ArrayCollection;
[Bindable
]private var _dataProvider:ArrayCollection;
private function init():void
{
_dataProvider = new ArrayCollection();
_dataProvider.addItem({title:"Louis Leakey"});
_dataProvider.addItem({title:"Mary Leakey"});
_dataProvider.addItem({title:"Richard Leakey"});
_dataProvider.addItem({title:"Margaret Mead"});
_dataProvider.addItem({title:"Jane Goodall"});
_dataProvider.addItem({title:"Ruth Benedict"});
_dataProvider.addItem({title:"Franz Boas"});
_dataProvider.addItem({title:"Claude Levi Strauss"});
_dataProvider.addItem({title:"Noam Chomsky"});
_dataProvider.addItem({title:"James Deetz "});
_dataProvider.addItem({title:"Sir Arthur Evans"});
_dataProvider.addItem({title:"Pere Bosch-Gimpera"});
_dataProvider.addItem({title:"Kathleen Kenyon"});
_dataProvider.addItem({title:"Colin Renfrew"});
_dataProvider.addItem({title:"Mortimer Wheeler"});
_dataProvider.addItem({title:"Leonard Woolley"});
_dataProvider.addItem({title:"Lewis Binford"});
_dataProvider.addItem({title:"Howard Carte"});
_dataProvider.addItem({title:"Donald Johansen"});
_dataProvider.addItem({title:"Phil Harding"});
_dataProvider.addItem({title:"Alfred Foucher"});
_dataProvider.addItem({title:"Heinrich Schliemann"});
_dataProvider.addItem({title:"Mick Aston"});
_dataProvider.addItem({title:"Barry Cunliffe"});
_dataProvider.addItem({title:"Churchill Babington"});
_dataProvider.addItem({title:"Vere Gordon Childe"});
_dataProvider.addItem({title:"Gustaf VI Adolf"});
}
public function getTitle(data:Object):String
{
return data.title;
}
]]>
</mx:Script>
<mx:HBox width="100%" height="200">
<controls:SmoothHorizontalScrollingList
dataProvider="{_dataProvider}"
width="100%" height="100%">
<controls:itemRenderer>
<mx:Component>
<mx:Text text="{outerDocument.getTitle(data)}"/>
</mx:Component>
</controls:itemRenderer>
</controls:SmoothHorizontalScrollingList>
</mx:HBox>
</mx:Application>
Good, but...
What I would like to do is make it so the list scrolls smoothtly without the the horizontal scroll bars visible. However, I can't yet figure out how to do this by changing just the horizontalScrollPosition, it seems to revert back to the default behavior for scrolling or blows up when you scroll from the right back to the left. I quickly began to see that to get a smooth scrolling list within Flex you would have to use an VBox or HBox container with a repeater. This method is outlined by Peter Ent "itemRenderers: Part 4: States and Transitions".
Update
Found an interesting post for overriding the scrollToIndex method of the HorizontalList control to allow for animated scrolling using external buttons, the only issue is that I couldn't scroll from the end of the list back to the beginning. I also found a blog post about built in animation effects for Flex 3 List controls using mx.effects.DefaultTileListEffect. However, it seems you need to use this only for a List or TileList component rather than a HorizontalList: itemsChangeEffect="{mx.effects.DefaultListEffect}" . I have not tried this method yet to animate a list, but it may prove promising if its actually what it claims to be.
Another Solution
Ben Clinkinbeard of "returned undefined;" blog and UM fame posted a great little example of a Smooth Scrolling List that takes a List control and wraps it in a Canvas container to achieve the smooth scrolling effect. This technique has the benefit of keeping the functionality of the list but also allowing you to smoothly scroll the list by using and external tween. It should be easy to convert this to a horizontal list and retain the smooth scrolling effect. Of course, the downside to this method is that you have to render the entire list, which wouldn't be very pleasant for large data sets, but for my needs this works great since my data set is very small. I have also experienced some resize issues when deleting items from the list. Here is an example of a Smooth Scrolling HorizontalList using a modified version of Ben's technique:
The code for the SmoothScrollingHorizontalList class:
/**
* Original code from Ben Clinkinbeard (http://www.returnundefined.com/2009/03/smooth-scrolling-flex-list);
* */
package com.
benclinkinbeard.
controls
{
import mx.
controls.
HorizontalList;
import mx.
core.
Container;
import mx.
core.
ScrollPolicy;
import mx.
core.
mx_internal;
import mx.
events.
FlexEvent;
public class SmoothScrollingHorizontalList extends HorizontalList
{
public function SmoothScrollingHorizontalList()
{
super();
// required to ensure all renderers get created
setStyle( "paddingRight", -1 );
// parent container will handle scrolling
horizontalScrollPolicy = ScrollPolicy.OFF;
addEventListener( FlexEvent.UPDATE_COMPLETE, handleUpdateComplete );
}
private function handleUpdateComplete( event:FlexEvent ):void
{
var combinedRendererWidth:Number = 0;
// iterate over list of renderers provided by our List subclass
// but since HorozintalList does not have variable column widths, we use columnWidth
for each( var renderer:Object in renderers )
{
combinedRendererWidth += columnWidth;
}
// list needs to be at least 10 pixels wide to show results
// and always needs to be 10 pixels wider than the combined width of the renderers
width = combinedRendererWidth + 10;
// need to shrink list height when canvas has a scrollbar so the scrollbar doesn't overlap the list
height = ( Container( parent ).maxHorizontalScrollPosition> 0 ) ? parent.height - 16 : parent.height;
// set the row height to height of list
rowHeight = height;
}
// array of renderers being used in this list
public function get renderers():Array
{
// prefix the internal property name with its namespace
var rawArray:Array = mx_internal::rendererArray;
var arr:Array = new Array();
// the rendererArray is a bit messy
// its an Array of Arrays, except sometimes the sub arrays are empty
// and sometimes it contains entries that aren't Arrays at all
for each( var obj:Object in rawArray )
{
var rendererArray:Array = obj as Array;
// make sure we have an Array and there is something in it
if( rendererArray && rendererArray.length> 0 )
{
// if there is something in it, the first item is our renderer
// but this doesn't seem to be the case for HorizontalList because obj[0] is always empty?
//arr.push( obj[ 0 ] );
arr = rendererArray;
}
}
return arr;
}
}
}
The source code for the example: smoothscrollinghorizontallist2.zip
-Mister

PhoneSeek is an application to track any device that uses InstaMapper. InstaMapper is free real-time GPS tracking service. To use InstaMapper you need a GPS enabled device with the free InstaMapper tracking software installed along with a API Key from InstaMapper's web site for each device you own. You can track mobile phones (iPhone, G1, Blackberry) or even automobiles.
Use Case
With PhoneSeek I can add my InstaMapper API Key for my phone and track it from the desktop. I can also add and track multiple devices. On my G1 phone, I installed the Android version of the InstaMapper tracking software. This software allows me to send my phone's GPS data to InstaMapper service where I can view it either on their website or consume the data through InstaMapper's web service. In PhoneSeek I just add the API Key to begin tracking the advice as soon as your phone starts to transmit GPS data.
Get PhoneSeek
Download the latest version from this location.
Development Background
PhoneSeek might be used to track devices from your family members, or maybe track down a lost or stolen phone. However, Phoneseek was basically created as a test project to learn PureMVC and also experiment with the new Google Maps API SWC for Adobe AIR. Previous versions of the Google Maps API SWC only worked for web-based applications.
Coming from Cairngorm to PureMVC was quite an experience. At first it was really painful because I had a tendency to over think things, or try to structure things more the Cairngorm ways (like dude, where are my delegates and commands). I really had a hard time understanding where to put certain code, like update code, or code to set the window location on startup, and even how to pass around value objects used by more than one view. It took some time to get used to the PureMVC way, but all in all, its not a bad framework to work with. However, I still prefer Cairngorm and have since started to investigate and really enjoy Mate.
The graphics used in the application were created by me or from DragonArtz Designs. I highly recommend the retro icon set from DragonArtz and give that site credit for the graphics I used. Sorry there is no support for the application since its just a demo of some of the features from InstaMapper and Google Maps for AIR.
Enjoy!
-Mister
Today, I was inspired to write a brief post about my beliefs towards the ideal work environment. I was inspired by an article about creators of Twitter and their experience coming from the early dot-com era of blow-out parties and slackers with bank rolls, to having a more mature dot-com. My favorite quote from a the article references the work culture:
Really, it’s like any other office, except there’s hardly anybody there. They’re all working at home. ... “There’s not a lot of foosball going on here,” CEO Evan Williams, 36, says. “People are working on what needs to be done. I know when I’ve been in those cultures, there’s just a lot more goofing off. That burns a lot of cycles that don’t need to be burned. Part of it’s an age thing. My first couple of companies, I only socialized with work people. A high percentage of people here have spouses, have families. They can go home.”
You can tell that Evan Williams really gets the whole "age" thing about the new type of dot-com work culture and understands that there is a paradigm shift in the programming workforce. We are moving from the young hungry programmers working 24/7 in drab caves and subsisting on only pizza and caffeine to new start up companies that have "seasoned" programmers living balanced lives while still producing quality work.
My background
I have been in large companies, education institutions, small companies, and even worked ( way too briefly ) for myself. I have had opportunities to experience a lot of different company cultures. For me it is usually the CEO that sets both the pace and the environment vibe for each company (especially if you are on your own). What I have always noticed is that most companies are reluctant to embrace new modes and thoughts for working. We live in a completely wired world with people and resources all over the planet, yet most employers still hold on to the belief that they need to see your face every day in order for you to accomplish your work. I think the real reason is simple, they don't believe it works or understand how it works. Despite the countless productivity studies to the contrary, if your boss doesn't see people in the office, they get freaked out, simple as that.
Why offices suck! ( mostly )
For me offices can be stressful for a few reasons. First, you have the drive and the stress from daily traffic can really wear you down, not to mention that it eats into your schedule. You see this all the time in Los Angeles, people living one to two hours or more away from the place they work. Countless hours on the road, the stress from grid lock, and the cost of fuel is costly in many ways. Second, people don't always work their best at the same set hours as others. Some programmers are night people, some programmers are morning people. To achieve optimum performance for programming you need to be aware of your body's own biorhythms, when your peak performance hours are and take advantage of that time to be creative and productive.
The third reason is just the general distractions, sure you can get distracted at home, but in the office you can't easily turn off those distractions. Programming requires intense concentration and an environment that allows for this. If you are at the office, you sit there in your cube or at your desk in a tiny little bubble and try to keep focused. But inevitably you will be distracted by others, interrupted with questions, meetings, or just the general movement of others around you that breaks your concentration. I think this is why most programmers sneak away at night and program at home.
My ultimate reason for disliking the office is "life". What I mean by that is that I have a life that is filled with life events. I might have to run to the doctor's, I might not feel well in the morning, I may have a UPS delivery, need run some errands, or take an important phone call. I can handle these life events at home without as much disruption as having to take off a whole morning from work to do them, fight the traffic, then rush back to the office. I can also multi-task at home with ease. I can have the TV on in the background for some comfort noise and catch up on my shows, I can pop in a load of laundry, or I can take a break to do dishes or prepare that nights meal.
For further examples, check out "Why must we still drive to work?" describing the benefits of working from home.
The "New" Seasoned work force
Workers have also changed. A good number of the people I have worked with have not been straight out of college, they are ex-dot.com people, people who are now older and more experienced. These workers have kids, they have family obligations, and enjoy time with people outside of work more than just co-workers. Having more seasoned workers also means you most likely have more responsible workers. Workers that will get the job done, don't mind stepping up to put in extra hours when a deadline is near. These workers are used to taking responsibility for their lives and having some control over their own destiny.
A recent article in Wired Magazine talks about the how most successful startup companies are founded by "veterans". The younger CEO's are the exception not the rule when it comes to a successful startup company. What this means is that the most creative and talented people in our industry are 30 somethings, and these 30 somethings often come with life baggage. They are past the "we got to build it now" mentality and are in a place to make better and more thoughtful decisions, this also applies to the veteran programmer as well. They are your real talent, the ones that can provide more than just code, they offer experience.
I know that when I was younger I was really fired up about working, I was in the office 80 hours a week, and it seemed the more work I accomplished, the more work I generated for myself. Once you work at that pace, its kind of like being hamster in a large hamster ball that never stops. I don't want to put down the person that loves to work at that pace, go for it, just be mindful that others have different styles and ways of working, and also remember life always changes. It is a good idea to learn how to manage expectations or your work pace will backfire and soon you will be burned out and used up.
What it could be like...
I have seen more and more places where upper management have been more relaxed about working from home, and if they don't agree with remote work, they at least expect people to go home to their families and lives. Usually management are also people with kids and family, they have out grown their college "all nighters", and know that spending time with people they care about on the weekends is a vital part of their happiness. They know that work never ends, so you have to set limits upon that work, a barrier so you can separate your work life from your family life. Even if you do find yourself stuck in a cube all day, there are ways to not get b