Pixel-perfect accuracy with FXG?

Flex 2 Comments »

I’ve been running some tests in Flex rendering FXG rects with a stroke to see if there’s a way to get the strokes to render inside the bounds of the object. I need this precision for a project I’m working on and unfortunately it doesn’t look good. All of these tests were done using some simple FXG code …

  1. <s:Rect
  2.    
  3.    width="30"
  4.    height="30"
  5.    radiusX="4"
  6.    radiusY="4"
  7.    >
  8.    <s:fill>
  9.     <s:SolidColor color="#ffffff" />
  10.    </s:fill>
  11.    <s:stroke>
  12.     <s:SolidColorStroke color="#ff3333" alpha="1" pixelHinting="true" weight="2" />
  13.    </s:stroke>
  14.   </s:Rect>

I’m not too concerned about the rounded corners but the lack of precision is an issue if you intend to create dynamic skins and want some control over the positions and dimensions of your composition.

Take a look to see my results :

Main.html.png @ 1600% (Layer 0, RGB/8*)
Uploaded with plasq‘s Skitch!
Main.html-2.png @ 1600% (Layer 0, RGB/8*)
Uploaded with plasq‘s Skitch!
Main.html-3.png @ 600% (Layer 0, RGB/8*)
Uploaded with plasq‘s Skitch!

You may need to jump to Skitch for that last one but basically if you have four squares in a HGroup with a 4 pixel gap and the square is defined as 30 pixels in width then you would expect an overall width of 132 pixels. You will actually get 140. This could be a big problem. 8 pixels is a big discrepancy.

Flash Catalyst and the Designer/Developer workflow

Flex 1 Comment »
Designer/Developer workflow
Uploaded with plasq‘s Skitch!
DesignerDeveloperWorkflow: Art Director / Interaction Designer
Uploaded with plasq‘s Skitch!
DesignerDeveloperWorkflow: Interaction Designer / Developer
Uploaded with plasq‘s Skitch!
DesignerDeveloperWorkflow: Interaction Designer / Developer 2
Uploaded with plasq‘s Skitch!
DesignerDeveloperWorkflow: Interaction Designer / Developer 3
Uploaded with plasq‘s Skitch!

I drew this up recently to illustrate a proposed workflow for a project I’m working on. This is similar to the workflow as proposed by Adobe but with an extra level of responsibility on the Interaction Designer. This being the requirement to adapt their Catalyst work in Flash Builder. This, in my opinion, is a more realistic overview of how most Flex framework projects actually play out nowadays.

Flash Catalyst is not quite powerful enough at the moment to produce flexible, bespoke skins. The chances are you’ll need to build components that aren’t just button skins. You need to write your own SkinnableComponent classes with custom skin parts that resize dynamically etc etc.

I’m afraid, if you want to be a shit-hot Interaction Designer and need to build amazing, re-usable prototypes that can quickly be integrated into the final architecture then you’re going to need to think outside of the Catalyst and get your hands dirty with Spark and the skinning component life-cycle.

Good luck!

There’s a PDF of the images on this post here.

Another custom Flex preloader but with RSL support

Actionscript, Flex 2 Comments »

This post has a great article on building a custom Flex preloader which also allows you to handle the progress of the runtime shared libraries that the Flex framework now loads into the users cache.

The DownloadProgressBar component allows you to view the progress of the runtime shared libraries but be aware that it can only tell you about the RSL as it loads and as there may be more than one it is practically impossible to get any indication of how many bytes in total the app is going to load. For this reason you may have seen erratic preloaders out there at the moment using all kinds of colourful language to tell you what it’s doing … “loading assets”, “modules”, “opening the champagne” etc. This is all done to keep the user occupied while each of the elements are loaded.

Then there’s the actual SWF file. Thankfully there are separate events to handle this so if you prefer you can still display the progress of the main SWF and then when it is completed change your display to inform the user that you are “initialising” which shouldn’t be too long after the main SWF has finished. Indeed your dependencies may have finished before the SWF.

I guess the only way to do this accurately is to know up front exactly how many bytes your application is going to load and then as you get the information from ProgressEvent.bytesLoaded from both the SWF and the RSL you can adjust your percentage calculation accordingly. If anyone does know how you can programmatically detect this I’d love to hear it.

Thanks to Bernhard Hirschmann and Jesse Warden for the work they’ve done and purely for my own reference here’s the class …

Custom Preloader for Flex with RSL support

  1. package
  2. {
  3.     import flash.display.MovieClip;
  4.     import flash.display.Sprite;
  5.     import flash.events.Event;
  6.     import flash.events.ProgressEvent;
  7.    
  8.     import mx.events.FlexEvent;
  9.     import mx.events.RSLEvent;
  10.     import mx.preloaders.DownloadProgressBar;
  11.  
  12.     /**
  13.      * This class extends the lightweight DownloadProgressBar class.  This class
  14.      * uses an embedded Flash 8 MovieClip symbol to show preloading.
  15.      * For handling both, SWF and RSL download, it separated its download progress
  16.      * by handling the appropriate events, i.e. RSLEvent
  17.      *
  18.      * @author jessewarden
  19.      * @author Bernhard Hirschmann (http://coding.bhirschmann.de)
  20.      */   
  21.     public class Preloader extends DownloadProgressBar
  22.     {
  23.        
  24.         /**
  25.          * The Flash 8 MovieClip embedded as a Class.
  26.          */       
  27.         [Embed(source="../assets/flash/preloader.swf", symbol="Preloader")]
  28.         private var FlashPreloaderSymbol:Class;
  29.        
  30.         private var clip:MovieClip;
  31.        
  32.     private var    isRslDownloading:Boolean = false;
  33.    
  34.         private var rslPercent:Number = 0;
  35.         private var swfPercent:Number = 0;
  36.        
  37.         private var rslBytesTotal:Number;
  38.         private var rslBytesLoaded:Number = 0;
  39.        
  40.         private var swfBytesTotal:Number;
  41.         private var swfBytesLoaded:Number = 0;
  42.        
  43.         public function Preloader()
  44.         {
  45.             super();
  46.            
  47.             // instantiate the Flash MovieClip, show it, and stop it.
  48.             // Remember, AS2 is removed when you embed SWF's,
  49.             // even "stop();", so you have to call it manually if you embed.
  50.             clip = new FlashPreloaderSymbol();
  51.             addChild(clip);
  52.             clip.gotoAndStop("start");
  53.         }
  54.        
  55.        public override function set preloader(preloader:Sprite):void
  56.     {                   
  57.       trace("starting...");
  58.  
  59.       // runtime shared library
  60.       preloader.addEventListener( RSLEvent.RSL_PROGRESS, onRSLDownloadProgress );
  61.       preloader.addEventListener( RSLEvent.RSL_COMPLETE, onRSLDownloadComplete );
  62.       preloader.addEventListener( RSLEvent.RSL_ERROR, onRSLError );
  63.  
  64.       // application
  65.       preloader.addEventListener( ProgressEvent.PROGRESS, onSWFDownloadProgress );   
  66.       preloader.addEventListener( Event.COMPLETE, onSWFDownloadComplete );
  67.      
  68.       // initialization
  69.       preloader.addEventListener( FlexEvent.INIT_PROGRESS, onFlexInitProgress );
  70.       preloader.addEventListener( FlexEvent.INIT_COMPLETE, onFlexInitComplete );
  71.      
  72.       clip.preloader.rsl_amount_txt.text = clip.preloader.app_amount_txt.text = "0%";
  73.      
  74.       centerPreloader();
  75.     }
  76.        
  77.     /**
  78.      * Makes sure that the preloader is centered in the center of the app.
  79.      *
  80.      */       
  81.     private function centerPreloader():void
  82.         {
  83.             x = (stageWidth / 2) - (clip.width / 2);
  84.             y = (stageHeight / 2) - (clip.height / 2);
  85.         }
  86.        
  87.     /**
  88.      * Updates the progress bar.
  89.      */
  90.     private function updateProgress():void
  91.     {
  92.       var p:Number = Math.round( (rslPercent + swfPercent) / 2 );
  93.         clip.preloader.gotoAndStop(p);
  94.     }
  95.    
  96.         /**
  97.          * As the RSL (runime shared library) (frame 2 usually) downloads, this event gets called.
  98.          * You can use the values from this event to update your preloader.
  99.          * @param event
  100.          *
  101.          */       
  102.         private function onRSLDownloadProgress( event:ProgressEvent ):void
  103.     {
  104.         isRslDownloading = true;
  105.        
  106.         rslBytesTotal = event.bytesTotal;
  107.         rslBytesLoaded = event.bytesLoaded;
  108.         rslPercent = Math.round( (rslBytesLoaded / rslBytesTotal) * 100);
  109.         trace("onRSLDownloadProgress: rslBytesLoaded " + rslBytesLoaded);
  110.         trace("onRSLDownloadProgress: rslBytesTotal " + rslBytesTotal);
  111.         trace("onRSLDownloadProgress: " + rslPercent + "%");
  112.         clip.preloader.rsl_amount_txt.text = String(rslPercent) + "%";
  113.        
  114.         updateProgress();
  115.     }
  116.    
  117.     /**
  118.      * When the download of frame 2
  119.      * is complete, this event is called. 
  120.      * This is called before the initializing is done.
  121.      * @param event
  122.      *
  123.      */       
  124.     private function onRSLDownloadComplete( event:RSLEvent ):void
  125.     {
  126.         trace("onRSLDownloadComplete: 100% - bytes total: " + event.bytesTotal);
  127.            clip.preloader.gotoAndStop(100);
  128.         clip.preloader.rsl_amount_txt.text = "100%";
  129.         rslPercent = 100;
  130.     }
  131.  
  132.     private function onRSLError( event:RSLEvent ):void
  133.     {
  134.         trace("onRSLError: " + event.errorText + " - " + event.url);
  135.         clip.preloader.status_txt.text = event.errorText;
  136.     }
  137.  
  138.         /**
  139.          * As the SWF (frame 2 usually) downloads, this event gets called.
  140.          * You can use the values from this event to update your preloader.
  141.          * @param event
  142.          *
  143.          */       
  144.         private function onSWFDownloadProgress( event:ProgressEvent ):void
  145.     {
  146.         swfBytesTotal = event.bytesTotal;
  147.         swfBytesLoaded = event.bytesLoaded;
  148.        
  149.         if ( isRslDownloading ) {
  150.           // as soon as RSL starts downloading the SWF data are added by the RSL values
  151.           swfBytesTotal -= rslBytesTotal;
  152.           swfBytesLoaded -= rslBytesLoaded;
  153.         }
  154.         swfPercent = Math.round( (swfBytesLoaded / swfBytesTotal) * 100);
  155.         trace("onSWFDownloadProgress: " + swfPercent + "%");
  156.         trace("onSWFDownloadProgress: swfBytesLoaded " + swfBytesLoaded);
  157.         trace("onSWFDownloadProgress: swfBytesTotal " + swfBytesTotal);
  158.         clip.preloader.app_amount_txt.text = String(swfPercent) + "%";
  159.  
  160.         updateProgress();
  161.     }
  162.    
  163.     /**
  164.      * When the download of frame 2
  165.      * is complete, this event is called. 
  166.      * This is called before the initializing is done.
  167.      * @param event
  168.      *
  169.      */       
  170.     private function onSWFDownloadComplete( event:Event ):void
  171.     {
  172.         trace("onSWFDownloadComplete: 100%");
  173.            clip.preloader.gotoAndStop(100);
  174.         clip.preloader.app_amount_txt.text = "100%";
  175.         swfPercent = 100;
  176.     }
  177.    
  178.     /**
  179.      * When Flex starts initilizating your application.
  180.      * @param event
  181.      *
  182.      */       
  183.     private function onFlexInitProgress( event:FlexEvent ):void
  184.     {
  185.         //trace("onFlexInitProgress: Initializing...");
  186.         try {
  187.           clip.preloader.gotoAndStop(100);
  188.           clip.preloader.status_txt.text = "Initializing...";
  189.         }
  190.         catch (e:Error) {
  191.         }
  192.     }
  193.    
  194.     /**
  195.      * When Flex is done initializing, and ready to run your app,
  196.      * this function is called.
  197.      *
  198.      * You're supposed to dispatch a complete event when you are done.
  199.      * I chose not to do this immediately, and instead fade out the
  200.      * preloader in the MovieClip.  As soon as that is done,
  201.      * I then dispatch the event.  This gives time for the preloader
  202.      * to finish it's animation.
  203.      * @param event
  204.      *
  205.      */       
  206.     private function onFlexInitComplete( event:FlexEvent ):void
  207.     {
  208.         trace("onFlexInitComplete");
  209.         clip.addFrameScript(21, onDoneAnimating);
  210.         clip.gotoAndPlay("fade out");
  211.     }
  212.    
  213.     /**
  214.      * If the Flash MovieClip is done playing it's animation,
  215.      * I stop it and dispatch my event letting Flex know I'm done.
  216.      * @param event
  217.      *
  218.      */       
  219.     private function onDoneAnimating():void
  220.     {
  221.         trace("onDoneAnimating");
  222.         clip.stop();
  223.         dispatchEvent( new Event( Event.COMPLETE ) );
  224.     }
  225.        
  226.     }
  227. }

Custom Preloader class for Flex apps

Actionscript, Flex No Comments »

I’ve blogged on this before but here’s an updated version of a custom preloader class that extends Sprite and loads in a MovieClip from a SWC to use as the animation and display the load progress.

Custom preloader for Flex

  1. package com.cravens.nr.preloader
  2. {
  3.         import com.cravens.nr.model.Config;
  4.        
  5.         import flash.display.GradientType;
  6.         import flash.display.Sprite;
  7.         import flash.events.Event;
  8.         import flash.events.ProgressEvent;
  9.         import flash.events.TimerEvent;
  10.         import flash.geom.Matrix;
  11.         import flash.utils.Timer;
  12.        
  13.         import mx.events.FlexEvent;
  14.         import mx.preloaders.IPreloaderDisplay;
  15.  
  16.         public class NRPreloader extends Sprite implements IPreloaderDisplay
  17.         {
  18.                 protected var loadingPanel:BusyPanel;
  19.                 protected var timer:Timer;
  20.                
  21.                 public function NRPreloader()
  22.                 {
  23.                         super();
  24.                 }
  25.                 //------------------------------------------------------------------------------
  26.                 public function set preloader(preloader:Sprite):void
  27.                 {
  28.                         preloader.addEventListener(ProgressEvent.PROGRESS, handleProgress);
  29.                         preloader.addEventListener(Event.COMPLETE, handleComplete);
  30.                        
  31.                         preloader.addEventListener(FlexEvent.INIT_PROGRESS, handleInitProgress);
  32.                         preloader.addEventListener(FlexEvent.INIT_COMPLETE, handleInitComplete);
  33.                 }
  34.                 //------------------------------------------------------------------------------
  35.                 public function initialize():void
  36.                 {
  37.                         var matrix:Matrix = new Matrix(0,1,1,0,0,0);
  38.                        
  39.                         graphics.beginGradientFill(GradientType.LINEAR,[0x000000,0x89265a],[1,1],[125,205],matrix);
  40.                         graphics.drawRect(0,0,Config.VIEWPORT_WIDTH,Config.VIEWPORT_HEIGHT);
  41.                        
  42.                         loadingPanel = new BusyPanel();
  43.                        
  44.                         try
  45.                         {
  46.                                 loadingPanel.heading_txt.text = "LOADING ... PLEASE WAIT";
  47.                                 loadingPanel.message_txt.text = "0/100%";
  48.                         }
  49.                         catch(e:Error)
  50.                         {
  51.                                 throw new Error("Asset does not contain the relevant textField objects");
  52.                         }
  53.                         finally
  54.                         {
  55.                                 loadingPanel.x = Config.VIEWPORT_CENTER_X-150;
  56.                                 loadingPanel.y = Config.VIEWPORT_CENTER_Y;
  57.                                
  58.                                 addChild(loadingPanel);
  59.                         }
  60.                 }
  61.  
  62.                
  63.                 //------------------------------------------------------------------------------
  64.                 // Define empty event listeners.
  65.                 private function handleProgress(event:ProgressEvent):void
  66.                 {
  67.                         var prog:Number = Math.ceil((event.bytesLoaded/event.bytesTotal)*100);
  68.                        
  69.                         loadingPanel.message_txt.text = prog+"/100%";
  70.                 }
  71.                 //------------------------------------------------------------------------------
  72.                 private function handleComplete(event:Event):void
  73.                 {
  74.                         loadingPanel.message_txt.text = "LAUNCHING";
  75.                 }
  76.                 //------------------------------------------------------------------------------
  77.                 private function handleInitProgress(event:FlexEvent):void
  78.                 {
  79.                 }
  80.                 //------------------------------------------------------------------------------
  81.                 private function handleInitComplete(event:Event):void
  82.                 {
  83.                         timer = new Timer(500,1);
  84.                         timer.addEventListener(TimerEvent.TIMER, dispatchComplete);
  85.                         timer.start();     
  86.                 }
  87.                
  88.                 //------------------------------------------------------------------------------
  89.                 private function dispatchComplete(event:TimerEvent):void
  90.                 {
  91.                         timer.removeEventListener(TimerEvent.TIMER, dispatchComplete);
  92.                         timer = null;
  93.                         dispatchEvent(new Event(Event.COMPLETE));
  94.                 }
  95.                
  96.                 //------------------------------------------------------------------------------
  97.                 // Implement IPreloaderDisplay interface
  98.                
  99.                 public function get backgroundColor():uint
  100.                 {
  101.                         return 0;
  102.                 }
  103.                 //------------------------------------------------------------------------------
  104.                 public function set backgroundColor(value:uint):void
  105.                 {
  106.                
  107.                 }
  108.                 //------------------------------------------------------------------------------
  109.                 public function get backgroundAlpha():Number
  110.                 {
  111.                         return 0;
  112.                 }
  113.                 //------------------------------------------------------------------------------
  114.                 public function set backgroundAlpha(value:Number):void
  115.                 {
  116.                 }
  117.                 //------------------------------------------------------------------------------
  118.                 public function get backgroundImage():Object
  119.                 {
  120.                         return undefined;
  121.                 }
  122.                 //------------------------------------------------------------------------------
  123.                 public function set backgroundImage(value:Object):void {
  124.                 }
  125.                 //------------------------------------------------------------------------------
  126.                 public function get backgroundSize():String {
  127.                         return "";
  128.                 }
  129.                 //------------------------------------------------------------------------------
  130.                 public function set backgroundSize(value:String):void {
  131.                 }
  132.                 //------------------------------------------------------------------------------
  133.                 public function get stageWidth():Number
  134.                 {
  135.                         return Config.VIEWPORT_WIDTH;
  136.                 }
  137.                 //------------------------------------------------------------------------------
  138.                 public function set stageWidth(value:Number):void
  139.                 {
  140.                 }
  141.                 //------------------------------------------------------------------------------
  142.                 public function get stageHeight():Number
  143.                 {
  144.                         return Config.VIEWPORT_HEIGHT;
  145.                 }
  146.                 //------------------------------------------------------------------------------
  147.                 public function set stageHeight(value:Number):void
  148.                 {
  149.                 }
  150.         }
  151. }

Also, notice I’ve created a simple grad background using the Sprite graphics …

  1. var matrix:Matrix = new Matrix(0,1,1,0,0,0);  
  2. graphics.beginGradientFill(GradientType.LINEAR,[0x000000,0x89265a],[1,1],[125,205],matrix);
  3. graphics.drawRect(0,0,Config.VIEWPORT_WIDTH,Config.VIEWPORT_HEIGHT);

This is the best way to do things because remember, the framework has not loaded while the preloader is displayed so you need to keep things lightweight and simple.

Also, the Matrix object rotates the grad so it runs vertically.

Have fun.

Detecting when text has run into the end of a container in a TextFlow object

Actionscript, Flex 3 Comments »

More fun and games with the Text Layout Framework. So, I have a load of links being flowed one by one through a bunch of linked containers. I need to detect when a link reaches the end and has ‘overflowed’ so to speak.

To do this you need to make sure your ContainerController objects have their ScrollPolicy set to OFF so the flowComposer recognises that the text is not in the container anymore. This will not halt the flow however so you need to add a nice bit of code that will detect if the FlowElement is no longer within a container.

Here’s the code …

Detecting when text has run into the end of the ContainerController in a TextFlow object

  1. /*
  2. A good idea to set a config object to pass into the TextFlow object when you create it
  3. */
  4. config = Configuration(TextFlow.defaultConfiguration).clone();
  5.  
  6. /*
  7. And add this overflowPolicy to it as well as your formatting defaults
  8. */
  9. config.overflowPolicy = OverflowPolicy.FIT_DESCENDERS;
  10.  
  11. textFlow = new TextFlow(config);
  12.  
  13. /*
  14.   Create your containers from sprites and make sure the scrollPolicy is OFF
  15. */
  16. var controller:ContainerController = new ContainerController(containerSprite,width,height);
  17. controller.verticalScrollPolicy = controller.horizontalScrollPolicy = ScrollPolicy.OFF;
  18.                                        
  19. textFlow.flowComposer.addController(controller);
  20.  
  21. /*
  22. Now imagine we are in a loop which adds a paragraph of lorem ipsum to the textflow and updates the controllers and checks if the flow is now out of the bounds of the container
  23. */
  24.  
  25. while(LOOP)
  26. {
  27.   textFlow.addChild(myParagraphElement); // you've created the paragraph
  28.   textFlow.flowComposer.updateAllControllers(); // you've created the controllers
  29.  
  30.   var containerIndex:int = textFlow.flowComposer.findControllerIndexAtPosition(textFlow.textLength-1);
  31.   if(containerIndex==-1) textFlowCompleteHandler();
  32. }

It’s these two lines that do the detection …

  1.  
  2. var containerIndex:int = textFlow.flowComposer.findControllerIndexAtPosition(textFlow.textLength-1);
  3.  
  4.   if(containerIndex==-1) textFlowCompleteHandler();

The findControllerIndexAtPosition method will return -1 if the character index passed in is not within a container. Remember to use textFlow.textLength-1 to get the last character. The textFlow variable is an instance of the TextFlow object.

The textFlowCompleteHandler can be any method that stops the insertion of FlowElement objects happening.

WP Theme & Icons by N.Design Studio
Entries RSS Comments RSS Log in