5/29/2011

Cover Flow

For some reason, people are crazy for a cover flow interface. It's actually not that complicated. Here is a quick run through.

Thinking it Through
To begin, let's look at it on the most basic level. Right off the bat, we know that it'll use an array of display objects. With that array, we'll also need a variable for us to keep track of the current image being shown. cover flow
And with this, all we need is a function that animates each of the thumbnails into its appropriate place. The image at showing in the display list will go to the center of the screen while the rest of the images' position and transformation will depend on its distance away from the current showing image.

Coding it
The following code creates the array of thumbnails.
Be aware that you'll have to supply your own array of image urls (thumbPaths). import flash.display.Sprite; import flash.display.Loader; import flash.net.URLRequest; var container:Sprite; // just a container to hold all the thumbnails var thumbs:Array; // array of thumbnail display objects (Sprites) var showing:int; // keeps track of the current showing thumbnail var tsize:int=200; // size of your thumbnail var thumbPaths:Array; // this is your array of image urls to load from createGallery(); function createGallery(){ thumbs=new Array(); container=new Sprite(); container.y=stage.stageHeight/2; addChild(container); var thumb:Sprite; var l:Loader; for(var i=0;i<thumbPaths.length;i++){ // create the thumbs thumb=new Sprite(); // draw background box with registration at center thumb.graphics.beginFill(0x333333); thumb.graphics.drawRect(-tsize/2,-tsize/2,tsize,tsize); thumb.name="t"+i; // use a Loader to load image, registration at center l=new Loader(); l.x=l.y=-tsize/2; l.load(new URLRequest(thumbPaths[i])); thumb.addChild(l); thumbs.push(thumb); container.addChild(thumb); } showing=0; }
Now that we've created our array, we need to make our display function to move and transform the pieces on stage. As previously stated, we need to determine how to place the image we are showing and then place the rest according to their distance from the main image being shown. It will all become apparent in the following display code. (Once again, I will be using my favorite tweening engine from GreenSock - TweenLite. import com.greensock.TweenLite; import com.greensock.plugins.TweenPlugin; import com.greensock.plugins.ColorMatrixFilterPlugin; TweenPlugin.activate([ColorMatrixFilterPlugin]); function moveToPlace(){ var diff:int; // distance from showing var dir:int; // direction from showing var gx:Number; // x position it will move to var scale:Number; // scale it will transform to var rotY:Number; // rotation Y var br:Number; // brightness for(var i:int=0;i<thumbs.length;i++){ diff=i-showing; // distance from showing dir=Math.abs(diff)/diff; // -1 or 1 if(i==showing){ gx=stage.stageWidth/2; // showing placed at center, scale 1, scale=1; // rotationY 0, and brightness 1 rotY=0; br=1; }else{ // 100 away from showing, then 30 more subsequently gx=stage.stageWidth+(100*dir)+(diff*30); // half scale, and 5% more subsequently, max 1.2 scale=0.5+Math.abs(diff)*0.05; if(scale>1.2)scale=1.2; // -90 or 90... see explanation below rotY=90*dir; // brightness 0.4 less subsequently, min -3 br=1-(Math.abs(diff)*0.4); if(br<-3)br=-3; } // tween to it TweenLite.to(thumbs[i],0.5,{colorMatrixFilter:{brightness:br},scaleX:scale,scaleY:scale,x:gx,rotationY:rotY}); } } note: All images are turned 90 degrees because all display objects will have a vanishing point at the center of the stage. As a result objects further from the center will have less rotation (which is what we want). A previous post explores this.

This moveToPlace function should be placed at the end of the createGallery function. It is also important to note that it is this function that makes it a cover flow interface. You can play with it to get it just the way you want it. You can even modify it to make it into a circular flow or a plain old linear gallery. The options are boundless and you just need some creativity and basic maths.

Depth sorting
If you were to run the code above, you'll immediately notice that thumbs to the right appear in front of the ones before it. What we need is for thumbs at showing to be at the top and subsequent ones away from it below it. To do this, we can just calculate each image's distance away from showing in the array. Something like:
function zOrderSort():void{ var obj:Object; var zArray:Array=new Array(); var l:int=thumbs.length; for(var i:int=0;i<l;i++){ // save thumb and its distance from showing into array obj={img:thumbs[i],dist:Math.abs(showing-i)}; zArray.push(obj); } //sort array by distance zArray.sortOn("dist",Array.NUMERIC|Array.DESCENDING); // assign depths according to sorted array for(i=0;i<l;i++){ container.setChildIndex(zArray[i].img,i); } } The above function just sorts the images by distance away from showing and then assigns depths accordingly. This z-depth function should run prior to the moveToPlace function. To make things easier, we can combine the two into one function: function show(){ zOrderSort(); moveToPlace(); }This show function should run after the creation of the gallery and after every update of showing.

Adding Interaction
Now we just need to add interaction to our sexy cover flow gallery. It is as simple as changing showing and then calling our show function.
For example, adding keyboard controls: import flash.events.KeyboardEvent; stage.addEventListener(KeyboardEvent.KEY_DOWN,keydown); function keydown(e:KeyboardEvent):void{ switch(e.keyCode){ case 39: // if press right key if(showing+1<thumbs.length){ showing++; show(); } break; case 37: // if press left key if(showing-1>-1){ showing--; show(); } break; } }
Or when clicking on a thumbnail: import flash.events.MouseEvent; ... // in thumb creation; in the createGallery function thumb.addEventListener(MouseEvent.CLICK,clickthumb); ... function clickthumb(e:MouseEvent):void{ var n:int=int(e.currentTarget.name.substr(1)); if(showing==n){ // when user clicks the thumb that is currently showing // do something (i.e. show full image) }else{ showing=n; show(); } } ...
TADA! (images are loaded from my Picasa account, here's how to do that)


Part 2: Changing it into a looping gallery

THE FULL CODE
Remember you still have to provide your own thumbPaths array. import flash.display.Sprite; import flash.display.Loader; import flash.net.URLRequest; import flash.events.KeyboardEvent; import flash.events.MouseEvent; import com.greensock.TweenLite; import com.greensock.plugins.TweenPlugin; import com.greensock.plugins.ColorMatrixFilterPlugin; TweenPlugin.activate([ColorMatrixFilterPlugin]); var container:Sprite; // just a container to hold all the thumbnails var thumbs:Array; // array of thumbnail display objects (Sprites) var showing:int; // keeps track of the current showing thumbnail var tsize:int=200; // size of your thumbnail var thumbPaths:Array; // this is your array of image urls to load from stage.addEventListener(KeyboardEvent.KEY_DOWN,keydown); createGallery(); function createGallery(){ thumbs=new Array(); container=new Sprite(); container.y=stage.stageHeight/2; addChild(container); var thumb:Sprite; var l:Loader; for(var i=0;i<thumbPaths.length;i++){ // create the thumbs thumb=new Sprite(); // draw background box with registration at center thumb.graphics.beginFill(0x333333); thumb.graphics.drawRect(-tsize/2,-tsize/2,tsize,tsize); thumb.name="t"+i; thumb.addEventListener(MouseEvent.CLICK,clickthumb); // use a Loader to load image, registration at center l=new Loader(); l.x=l.y=-tsize/2; l.load(new URLRequest(thumbPaths[i])); thumb.addChild(l); thumbs.push(thumb); container.addChild(thumb); } showing=0; show(); } function moveToPlace(){ var diff:int; // distance from showing var dir:int; // direction from showing var gx:Number; // x position it will move to var scale:Number; // scale it will transform to var rotY:Number; // rotation Y var br:Number; // brightness for(var i:int=0;i<thumbs.length;i++){ diff=i-showing; // distance from showing dir=Math.abs(diff)/diff; // -1 or 1 if(i==showing){ gx=stage.stageWidth/2; scale=1; rotY=0; br=1; }else{ gx=250+(100*dir)+(diff*30); scale=0.5+Math.abs(diff)*0.05; if(scale>1.2)scale=1.2; rotY=90*dir; br=1-(Math.abs(diff)*0.4); if(br<-3)br=-3; } // tween to it TweenLite.to(thumbs[i],0.5,{colorMatrixFilter:{brightness:br},scaleX:scale,scaleY:scale,x:gx,rotationY:rotY}); } } function zOrderSort():void{ var obj:Object; var zArray:Array=new Array(); var l:int=thumbs.length; for(var i:int=0;i<l;i++){ // save thumb and its distance from showing into array obj={img:thumbs[i],dist:Math.abs(showing-i)}; zArray.push(obj); } //sort array by distance zArray.sortOn("dist",Array.NUMERIC|Array.DESCENDING); // assign depths according to sorted array for(i=0;i<l;i++){ container.setChildIndex(zArray[i].img,i); } } function show(){ zOrderSort(); moveToPlace(); } function keydown(e:KeyboardEvent):void{ switch(e.keyCode){ case 39: // if press right key if(showing+1<thumbs.length){ showing++; show(); } break; case 37: // if press left key if(showing-1>-1){ showing--; show(); } break; } } function clickthumb(e:MouseEvent):void{ var n:int=int(e.currentTarget.name.substr(1)); if(showing==n){ // when user clicks the thumb that is currently showing // do something (i.e. show full image) }else{ showing=n; show(); } }

5 comments:

  1. Hello!

    Great Blog! Thanks for sharing... Is this compatible with blogspot? How can I place it on my Blogspot page?
    Thank you very much!

    ReplyDelete
  2. @dslmse: - take a look here:
    http://translate.google.com/translate?sl=auto&tl=en&u=http://bestar-space.blogspot.com/search/label/Blog%20Tutorial#content

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Great articles on the two coverflow galleries, just modified it to load from xml. Had an issue with the images showing but after I'd initially written it I spotted it was the fault of the perspective setting in scene so all good.

    Nice job and well covered with the other relevant posts, plus good inclusion of the picasa for added useability.

    ReplyDelete