Image loading / preloading in Flex

error77
error77 used Ask the Experts™
on
Hi all,

I'm loading some online images and it works fine but when i use the slider to scroll the images it jumps from one to the other.
How could I avoid that please. Either preloading or any other way.
Here is the current code:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" initialize="loadXML()">
      <mx:Image source="{pics.pic[Math.floor(picSlider.value)].imageurl}"/>
      <mx:Label text="{pics.pic[Math.floor(picSlider.value)].title}"/>
      <mx:Script><![CDATA[
            [Bindable]
            private var pics:XML;
            private function loadXML():void{
                  var loader:URLLoader = new URLLoader();
                  var request:URLRequest = new URLRequest("http://mydomain.com/flash/pics.xml");
                  loader.addEventListener(Event.COMPLETE, onComplete);
                  loader.load(request);
            }
            private function onComplete(event:Event):void {
                  pics = new XML(event.target.data);
            }
      ]]></mx:Script>
     
      <mx:HSlider id="picSlider" minimum="0" maximum="3.9" value="1"/>
</mx:Application>


And the xml looks like this:

<?xml version="1.0" encoding="UTF-8"?>

<pics>
  <pic>
    <imageurl>http://mydomain.com/flash/image1.jpg</imageurl>
    <title>iamge1</title>
  </pic>

 <pic>
    <imageurl>http://mydomain.com/flash/image2.jpg</imageurl>
    <title>iamge2</title>
  </pic>
</pics>

Thanks
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
The Image and Label components cannot recognize the float values sent with HSlider component CHANGE event. It would be better if you use some container (Box for example) with horizontalScrollPolicy=auto where you will add as much Image and Label components as XML data require.
If you still will need HSlider, it would be better to use Canvas and Box inside it, so that when the slider value's changed - the Box x position goes left and it looks like scrolling with HSlider.

Commented:
This seems to do the trick. It gets rid of the data binding on the XML, too, which is always a good thing.
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" initialize="loadXML()" width="100%" height="100%">
    <mx:Script><![CDATA[
        private var pics:XML;
        private var even:Boolean;
        private var lastIndex = -1;

        private function loadXML():void {
            var loader:URLLoader = new URLLoader();
            var request:URLRequest = new URLRequest("http://localhost:9080/images/pics.xml");
            loader.addEventListener(Event.COMPLETE, onComplete);
            loader.load(request);
        }

        private function onComplete(event:Event):void {
            pics = new XML(event.target.data);
            picSlider.maximum = pics.children().length() - .1;
            updateImages(null);
        }

        private function updateImages(event:Event):void {
            var idx:int = Math.floor(picSlider.value);
            if (idx != lastIndex) {
                even = !even;
                evenImage.visible = even;
                oddImage.visible = !even;
                if (even) {
                    evenImage.source = pics.pic[idx].imageurl;

                } else {
                    oddImage.source = pics.pic[idx].imageurl;
                }
                picLabel.text = pics.pic[idx].title;
                lastIndex = idx;
            }
        }
        ]]></mx:Script>
    <mx:Fade id="fadeOut" duration="1000" alphaFrom="1.0" alphaTo="0.0"/>
    <mx:Fade id="fadeIn" duration="1000" alphaFrom="0.0" alphaTo="1.0"/>
    <mx:HSlider id="picSlider" minimum="0" maximum="3.9" value="0" change="updateImages(event)" liveDragging="true"/>
    <mx:Label id="picLabel"/>
    <mx:Canvas width="100%" height="100%">
        <mx:Image id="oddImage" showEffect="{fadeIn}" hideEffect="{fadeOut}"/>
        <mx:Image id="evenImage" showEffect="{fadeIn}" hideEffect="{fadeOut}"/>
    </mx:Canvas>
</mx:Application>

Open in new window

Commented:
To be on the safe side with this, you might change the mx:Application's initialize="loadXML()" to creationComplete="loadXML()". That way, you won't get a null object reference on the off-chance that the client loads the XML before the slider and image objects have been created.
Introduction to Web Design

Develop a strong foundation and understanding of web design by learning HTML, CSS, and additional tools to help you develop your own website.

Commented:
OK, one more thing. With liveDragging="true" on the slider, it is possible to change from one image to the other and back in such a way that you change the visibility from false to true on an image while it is running the fadeOut effect, and you end up with nothing visible.

If you leave liveDragging at the default value of false, that situation should be next to impossible, particularly if you set the Fade durations a little shorter, say 500. Shorter liveDragging durations will also make the situation less likely if you do want to go with liveDragging.

Commented:
I've been playing with this a bit (because it's more fun than what I should be doing) and I've come up with something that handles the interrupted fadeOut effect pretty well. Here it is:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="loadXML()"
                width="100%" height="100%">
    <mx:Script><![CDATA[
        import mx.events.EffectEvent;

        private var pics:XML;
        [Bindable]
        private var even:Boolean;
        private var lastIndex:int = -1;
        private var currentIndex:int = 0;

        private function loadXML():void {
            var loader:URLLoader = new URLLoader();
            var request:URLRequest = new URLRequest("http://localhost:9080/images/pics.xml");
            loader.addEventListener(Event.COMPLETE, onComplete);
            loader.load(request);
        }

        private function onComplete(event:Event):void {
            pics = new XML(event.target.data);
            picSlider.maximum = pics.children().length() - .1;
            updateImages(null);
        }

        private function updateImages(event:Event):void {

            currentIndex = Math.floor(picSlider.value);
            if (currentIndex != lastIndex) {

                var go:Boolean = true;
                if (fadeOut.isPlaying) {
                    fadeOut.addEventListener(EffectEvent.EFFECT_END, toggleImages);
                    go = false;
                } else {
                    toggleImages(null);
                }

            }

        }

        private function toggleImages(event:EffectEvent):void {
            if (event != null) {
                event.target.removeEventListener(event.type, toggleImages);
                event.target.reverse();
            }
            if (lastIndex != currentIndex) {
                even = !even;
                evenImage.visible = even;
                oddImage.visible = !even;
                if (even) {
                    evenImage.source = pics.pic[currentIndex].imageurl;
                } else {
                    oddImage.source = pics.pic[currentIndex].imageurl;
                }
                picLabel.text = lastIndex + ". " + pics.pic[currentIndex].title;
                lastIndex = currentIndex;
            }

        }

        ]]></mx:Script>

    <mx:Fade id="fadeOut" duration="1000" alphaFrom="1.0" alphaTo="0.0"/>
    <mx:Fade id="fadeIn" duration="1000" alphaFrom="0.0" alphaTo="1.0"/>

    <mx:HSlider id="picSlider" minimum="0" maximum="3.9" value="0" change="updateImages(event)" liveDragging="true"/>
    <mx:Label id="picLabel"/>
    <mx:Canvas width="100%" height="100%">
        <mx:Image id="oddImage" showEffect="{fadeIn}" hideEffect="{fadeOut}"/>
        <mx:Image id="evenImage" showEffect="{fadeIn}" hideEffect="{fadeOut}"/>
    </mx:Canvas>
</mx:Application>

Open in new window

Author

Commented:
Hi petiex, sorry about the delay, just arrived :o)
It's a nice effect and a lot better but the issue here is that I need to preload the images or as many as i can.
The idea is that it's going  to have to handle lots of them, so if preloaded it will be really quick.
Does it make sense?

Thanks

Commented:
OK, here it is again with a preloadImages function that you invoke right after you load the pics xml. This function loops through the urls, creating, adding, and removing an Image component with that as its source. This gets all the images into browser cache. You can test by clearing your cache and watching the Net activity with the FireFox FireBug plug-in, which, if you don't have, you should get (it's free) if you are doing flex development.
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="loadXML()"
                width="100%" height="100%">
    <mx:Script><![CDATA[
        import mx.controls.SWFLoader;
        import mx.events.EffectEvent;

        private var pics:XML;
        [Bindable]
        private var even:Boolean;
        private var lastIndex:int = -1;
        private var currentIndex:int = 0;

        private function loadXML():void {
            var loader:URLLoader = new URLLoader();
            var request:URLRequest = new URLRequest("http://localhost:9080/images/pics.xml");
            loader.addEventListener(Event.COMPLETE, onComplete);
            loader.load(request);
        }

        private function onComplete(event:Event):void {
            pics = new XML(event.target.data);
            picSlider.maximum = pics.children().length() - .1;
            preloadImages();
            updateImages(null);
        }

        private function preloadImages():void {
            for each(var x:XML in pics.pic) {
                var loader:SWFLoader = new SWFLoader();
                loader.source = x.imageurl;
                this.addChild(loader);
                this.removeChild(loader);
            }
        }

        private function updateImages(event:Event):void {

            currentIndex = Math.floor(picSlider.value);
            if (currentIndex != lastIndex) {

                var go:Boolean = true;
                if (fadeOut.isPlaying) {
                    fadeOut.addEventListener(EffectEvent.EFFECT_END, toggleImages);
                    go = false;
                } else {
                    toggleImages(null);
                }

            }

        }

        private function toggleImages(event:EffectEvent):void {
            if (event != null) {
                event.target.removeEventListener(event.type, toggleImages);
                event.target.reverse();
            }
            if (lastIndex != currentIndex) {
                even = !even;
                evenImage.visible = even;
                oddImage.visible = !even;
                if (even) {
                    evenImage.source = pics.pic[currentIndex].imageurl;
                } else {
                    oddImage.source = pics.pic[currentIndex].imageurl;
                }
                picLabel.text = lastIndex + ". " + pics.pic[currentIndex].title;
                lastIndex = currentIndex;
            }

        }

        ]]></mx:Script>

    <mx:Fade id="fadeOut" duration="1000" alphaFrom="1.0" alphaTo="0.0"/>
    <mx:Fade id="fadeIn" duration="1000" alphaFrom="0.0" alphaTo="1.0"/>

    <mx:HSlider id="picSlider" minimum="0" maximum="3.9" value="0" change="updateImages(event)" liveDragging="true"/>
    <mx:Label id="picLabel"/>
    <mx:Canvas width="100%" height="100%">
        <mx:Image id="oddImage" showEffect="{fadeIn}" hideEffect="{fadeOut}"/>
        <mx:Image id="evenImage" showEffect="{fadeIn}" hideEffect="{fadeOut}"/>
    </mx:Canvas>
</mx:Application>

Open in new window

Author

Commented:
Thanks :o) That's better. The only problem that I have with it is the it flickers between images.
If the images are preloaded, shouldn't it not do that?
If it just didn't have that flickering it would be great ... can that be avoided?

Commented:
I don't get any flickering. Without the preloader, however, the image sometimes finishes loading after the fadeIn effect completes. Is it possible your image server is adding no-cache directives in the header? I'll attach the XML I am using, a few wikipedia images, which caches for me with no problem. Do you get the same behavior with that?
<pics>
            <pic> <imageurl>http://www.wunderkabinett.co.uk/gallery/albums/userpics/10002/SmallWorlds2.JPG</imageurl> <title>Flower</title> </pic>
            <pic> <imageurl>http://upload.wikimedia.org/wikipedia/commons/3/33/BennyGoodmanandBandStageDoorCanteen.jpg</imageurl> <title>Benny Goodman</title></pic>
            <pic> <imageurl>http://upload.wikimedia.org/wikipedia/commons/2/20/La_china.jpg</imageurl> <title>Lady</title> </pic>
            <pic> <imageurl>http://www.iconarchive.com/icons/visualpharm/animals/256/Elephant-icon.png</imageurl> <title>Elephant</title> </pic>
      </pics>

Open in new window

Author

Commented:
cant get this data to load. Will keep trying

Author

Commented:
hmm still not able to use your xml. Any other ideas pls?

Commented:
I don't know whether the flickering is from the images not caching or from events tripping over each other in the actionscript.

Can you inspect the response headers you get back from the image requests you are able to load?
If they look anything like this:

Expires: <Some date in the past>
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache

then, you need to change the headers the server is setting on the images.

Does the flickering still happen if you set liveDragging=false on the slider?

Author

Commented:
Will check it out asap

Author

Commented:
Thanks!

Commented:
Out of curiosity, what was the issue? Were the images not caching, or did you just decide to go with liveDragging=false? Or a combination? Thanks for the expert poinks!

Author

Commented:
Well, a combination of both. Thanks a lot! Ps: I don't suppose you could help me on my other question?
It's basically the same code but I need to search the XML file/nodes... I cannot find anything on google on that :-/
Thanks anyway!

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial