The BitmapFader class, a generic approach for smooth transitions

This post demonstrates how to make smooth transitions when a component view changes. I used this technique in the FactBook Demo and in Filemap (the AIR sample bundled with ILOG Elixir).

This behaviour is implemented by the BitmapFader class using Flex bitmap manipulation capabilities. Suppose you have a UIComponent (in this example a map) for which data changes on user interaction. Here is the steps for this graphic effect:

  1. BitmapFader creates a bitmap snapshoot of the component and draws that snapshoot over the component, the component is hidden.
  2. The component changes its view (it can not be viewed at this point).
  3. Finally BitmapFader redraws the snapshoot several times using a tween object. On each redraw, alpha channel of the bitmap is decreased, the underlying component progressively appears.

BitmapFader class provides the following API:

  • component:UIComponent allows to set the target component
  • lock() is called just before the code which changes the view of the component
  • release() is called just after that code to start the transition

Source code of the above example

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
  xmlns:ilog="http://www.ilog.com/2007/ilog/flex"
  layout="vertical" xmlns:local="*"
  backgroundColor="0xFFFFFF"
  backgroundGradientAlphas="[]">
  <mx:Script>
    <![CDATA[
      import ilog.maps.MapFeature;
      import mx.graphics.Stroke;

      private function normalTransition():void
      {
        changeMapColors();
      }

      private function smoothTransition():void
      {
        fader.lock();
        changeMapColors();
        fader.release();
      }

      private function changeMapColors():void
      {
        var mf:MapFeature;
        for each (var key:String in map.featureNames)
        {
          mf = map.getFeature(key);
          mf.setStyle("fill", Math.random() * 255 * 255 * 255);
        }
      }
    ]]>
  </mx:Script>
  <local:BitmapFader id="fader" component="{map}" duration="500"/>
  <ilog:USStatesMap id="map" width="100%" height="100%"
    stroke="{new Stroke(0xAAAAAA, 0)}" backgroundColor="0xFFFFFF"/>
  <mx:HBox width="100%" horizontalAlign="center">
    <mx:Button label="Normal transition" click="normalTransition()"/>
    <mx:Button label="Smooth transition" click="smoothTransition()"/>
  </mx:HBox>
</mx:Application>

Source code of BitmapFader

package
{
  import flash.display.BitmapData;
  import flash.display.Graphics;
  import flash.events.Event;
  import flash.geom.ColorTransform;
  import flash.geom.Rectangle;

  import mx.core.UIComponent;
  import mx.effects.Tween;
  import mx.events.ResizeEvent;

  public class BitmapFader extends UIComponent
  {
    private var _component:UIComponent;
    private var _lock:Boolean;
    private var _initialBD:BitmapData;
    private var _fadingBD:BitmapData;
    private var _duration:Number = 300;
    private var _tween:Tween;

    public function lock():void
    {
      if (_component == null)
        return;
      _initialBD = new BitmapData(_component.width, _component.height, true, 0x00FFFFFF);
      _initialBD.draw(_component);
      var g:Graphics = graphics;
      g.clear();
      g.beginBitmapFill(_initialBD, null, false);
      g.drawRect(0, 0, unscaledWidth, unscaledHeight);
    } 

    public function get duration():Number
    {
      return _duration;
    }

    public function set duration(value:Number):void
    {
      _duration = value;
    } 

    public function get component():UIComponent
    {
      return _component;
    }

    public function set component(value:UIComponent):void
    {
      if (_component != null)
        _component.removeChild(this);
      _component = value;
      _component.addChild(this);

      setActualSize(_component.width, _component.height);
      _component.addEventListener(ResizeEvent.RESIZE, onCompResize);

    }

    private function onCompResize(e:ResizeEvent):void
    {
      setActualSize(_component.width, _component.height);
      validateNow();
    }

    public function release():void
    {
      if (_component == null)
        return;
      _fadingBD = new BitmapData(_component.width, _component.height, true, 0x00FFFFFF);
      _tween = new Tween(this, 1, 0, _duration, 15);
    }

    public function onTweenEnd(v:Number):void
    {
      graphics.clear();
    }

    public function onTweenUpdate(v:Number):void
    {
      var ct:ColorTransform = new ColorTransform(1.0, 1.0, 1.0, v);

      _fadingBD.fillRect(new Rectangle(0, 0, _component.width, _component.height), 0x00000000);
      _fadingBD.draw(_initialBD, null, ct);
      var g:Graphics = graphics;
      g.clear();
      g.beginBitmapFill(_fadingBD, null, false);
      g.drawRect(0, 0, unscaledWidth, unscaledHeight);
    }
  }
}

BitmapFader class works for any graphic component, but it is especially interesting for components such as maps or treemaps for which only colors change when data change.

Bookmark: These icons link to social bookmarking sites where readers can share and discover new web pages.
  • Digg
  • del.icio.us
  • Furl
  • Slashdot
  • StumbleUpon
  • Technorati

Tags: , , ,

Leave a Reply