Over the last few days I have been posting about my first custom layout in the Spark component architecture, PagedLayout (day 1, day 2). Today I thought I’d post a real world mashup using the layout as well as take a little more time to explain what’s going on with the class and how you can use it in your projects. Since I didn’t have time yesterday to post the changes I made between the animated and non-animated versions I’ll go through them now.
I am using the GreenSock TweenLite libraries to animate the transitions between pages. The layout allows you to specify three key properties to manage the animation without having to modify the underlying layout class. Notice these lines in the Flickr mashup:
-
<!– Our DataGroup bound to our itemList data provider with the Paged Layout applied –>
-
<s:DataGroup id="datagroup" height="128" verticalCenter="-1"
-
dataProvider="{photoFeed}" itemRenderer="itemRenderer"
-
width="446" horizontalCenter="2">
-
<s:layout>
-
<adam:PagedLayout id="myLayout" itemPadding="10"
-
animationDuration="0.75"
-
easingType="{PagedLayout.EASING_DEFAULT}"
-
useAnimation="true" />
-
</s:layout>
-
</s:DataGroup>
PagedLayout allows you to specify whether or not it uses animation to switch between pages with the useAnimation property. You can set this property to true or false.
The animationDuration property is the number of seconds it takes the transition to complete.
The easingType property is a string place holder for the type of easing you wish to use in the animation. There is currently support for Elastic, Bounce, Quadratic, and Cubic – the respective constants are EASING_ELASTIC, EASING_BOUNCE, EASING_DEFAULT, and EASING_CUBIC. Take a quick look at the PagedLayout.as class file and you can see how easy it is to add a new type of easing as an option. All easing included with TweenLite is supported, just use what is already there as an example and it should take only a few seconds to add in other easing equations.
I used the same method as Evtim in his WheelLayout Mashup post to connect with Flickr. This method downloads up to 20 photos from Flickr’s recent uploads RSS feed to use them as elements in the layout. Below is the final version with a link to view it in a full browser window and a direct link to the source code. As always, free for personal use, and commercial use (if you drop me an e-mail).
Wow, very slick 🙂
Very nice, but it doesn’t seam to work with List component, does it?
It’s awesome, nice illustration of what can be achieved with layouts, would be even better if it worked with height, if you increase height of item it would populate as TileLayout with pagination. Great work again
Paolo, I have not tried with the List component, I may get around to testing and fixing at some point. If you get it to work please comment again.
Tyrone, thanks for your comment, yes I think a tiled version would be a great component to start work on.
I found a way to use it with a List component, just replace
element = layoutTarget.getElementAt(index);
with
element = useVirtualLayout ? layoutTarget.getVirtualElementAt(index) : layoutTarget.getElementAt(index);
Paolo, thanks for this, I’ll change the code so that it work properly when I get the time.
Vertical Layout Version
have a enjoy
package com.layout
{
/*************************************************************************
Copyright 2009 Adam Bergman (adambergman.com).
You are free to use this code in any personal or commercial projects.
I only ask that if you intend to use this code in any commercial project,
please contact me via adambergman.com and let me know.
*************************************************************************/
import flash.display.DisplayObject;
import gs.TweenLite;
import gs.easing.*;
import mx.core.ILayoutElement;
import spark.components.supportClasses.GroupBase;
import spark.layouts.supportClasses.LayoutBase;
public class VPagedLayout extends LayoutBase
{
[Bindable] public static var EASING_BOUNCE:String = “bounce”;
[Bindable] public static var EASING_ELASTIC:String = “elastic”;
[Bindable] public static var EASING_DEFAULT:String = “”;
[Bindable] public static var EASING_CUBIC:String = “cubic”;
// Private variables for our properties
private var _currentPage:Number = 1;
private var _requestedPage:Number = 1;
private var _totalPages:Number = 1;
private var _itemPadding:Number = 10;
private var _isAnimating:Boolean = false;
private var _animationDuration:Number = 1;
private var _useAnimation:Boolean = true;
private var _easingFunction:Function = Quad.easeOut;
private var _easingType:String = EASING_DEFAULT;
public function get easingType():String
{
return _easingType;
}
public function set easingType(v:String):void
{
switch(v)
{
case EASING_BOUNCE: this.easingFunction = Bounce.easeOut; break;
case EASING_ELASTIC: this.easingFunction = Elastic.easeOut; break;
case EASING_DEFAULT: this.easingFunction = Quad.easeOut; break;
case EASING_CUBIC: this.easingFunction = Cubic.easeOut; break;
default: this.easingFunction = Linear.easeNone; break;
}
_easingType = v;
}
private function get easingFunction():Function
{
return _easingFunction;
}
private function set easingFunction(v:Function):void
{
_easingFunction = v;
}
public function VPagedLayout()
{
super();
this.clipAndEnableScrolling = true;
}
[Bindable]
[Bindable] public function get useAnimation():Boolean
{
return _useAnimation;
}
public function set useAnimation(v:Boolean):void
{
_useAnimation = v;
}
[Bindable] public function get animationDuration():Number
{
return _animationDuration;
}
public function set animationDuration(v:Number):void
{
_animationDuration = v;
}
[Bindable] public function get isAnimating():Boolean
{
return _isAnimating;
}
private function set isAnimating(v:Boolean):void
{
_isAnimating = v;
}
[Bindable] public function get requestedPage():Number
{
return _requestedPage;
}
public function set requestedPage(v:Number):void
{
_requestedPage = v;
}
public function get itemPadding():Number
{
return _itemPadding;
}
public function set itemPadding(v:Number):void
{
_itemPadding = v;
var layoutTarget:GroupBase = target;
if (!layoutTarget)
return;
layoutTarget.invalidateDisplayList();
}
[Bindable] public function get totalPages():Number
{
return _totalPages;
}
private function set totalPages(v:Number):void
{
_totalPages = v;
}
[Bindable] public function get currentPage():Number
{
return _currentPage;
}
private function set currentPage(v:Number):void
{
_currentPage = v;
}
private var switchIt:Boolean = false;
// Move to the Next Page
public function next():void
{
if(this.isAnimating){ return; }
updatePageCount();
if(this.currentPage + 1 > this.totalPages)
{
this.requestedPage = 1;
switchIt = true;
}else{
this.requestedPage++;
}
var layoutTarget:GroupBase = target;
if (!layoutTarget)
return;
layoutTarget.invalidateDisplayList();
}
// Move to the Previous Page
public function previous():void
{
if(this.isAnimating){ return; }
updatePageCount();
if(this.currentPage – 1 == 0)
{
this.requestedPage = this.totalPages;
switchIt = true;
}else{
this.requestedPage–;
}
var layoutTarget:GroupBase = target;
if (!layoutTarget)
return;
layoutTarget.invalidateDisplayList();
}
// Examine the width of the container and all layout elements
// and determine the number of pages
private function updatePageCount():void
{
var layoutTarget:GroupBase = target;
if (!layoutTarget)
return;
var element:ILayoutElement;
var pageCount:Number = 1;
var totalHeight:Number = 0;
for (var index:int = 0; index layoutTarget.height)
{
pageCount++;
totalHeight = element.getLayoutBoundsHeight() + _itemPadding;
}
}
this.totalPages = pageCount;
}
// Returns true if the given “testElement” exists on the the given page “pageNumber”
private function isElementOnPage(testElement:ILayoutElement, pageNumber:Number):Boolean
{
var layoutTarget:GroupBase = target;
if (!layoutTarget)
return false;
var element:ILayoutElement;
var pageCount:Number = 1;
var totalHeight:Number = 0;
var layoutHeight:Number = layoutTarget.height + layoutTarget.getStyle(“paddingTop”) + layoutTarget.getStyle(“paddingBottom”);
for (var index:int = 0; index layoutTarget.height)
{
pageCount++;
totalHeight = element.getLayoutBoundsHeight() + _itemPadding;
}
if(element == testElement)
{
if(pageCount == pageNumber){ return true; }else{ return false; }
}
}
return false;
}
// This is the method that actually moves layout elements into position
private function createPage(pageNum:Number, offset:Number=0, hideItems:Boolean=true):void
{
// hack to check if the current Page Number requested is less than or equal to
// the total amount of available pages (in case of window or component resize)
if(pageNum > this.totalPages){ pageNum = this.totalPages; this.currentPage = pageNum; }
var layoutTarget:GroupBase = target;
if (!layoutTarget)
return;
var element:ILayoutElement;
var totalHeight:Number = 5;
for (var index:int = 0; index this.currentPage)
{
offsetMultiplier = -1;
}else{
offsetMultiplier = 1;
}
if(switchIt)
{
offsetMultiplier *= -1;
}
var offset:Number = (layoutTarget.height + 10) * offsetMultiplier;
createPage(this.currentPage);
createPage(this.requestedPage, offset, false);
var element:ILayoutElement;
for (var index:int = 0; index < target.numElements; index++)
{
element = target.getElementAt(index);
if( !element.includeInLayout )
continue;
if(isElementOnPage(element, this.currentPage) || isElementOnPage(element, this.requestedPage))
{
TweenLite.to(DisplayObject(element), this.animationDuration, {y: DisplayObject(element).y + (offset), ease: this.easingFunction});
}
}
TweenLite.delayedCall(this.animationDuration, animationDone);
}
private function animationDone():void
{
this.isAnimating = false;
this.currentPage = this.requestedPage;
}
// Override the updateDisplayList method and call our sub methods
override public function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
var layoutTarget:GroupBase = target;
if (!layoutTarget)
return;
updatePageCount();
if(this.totalPages < 1){ return; }
if(this.requestedPage != this.currentPage && this.useAnimation)
{
if(!this.isAnimating)
{
animatePages(this.switchIt);
this.switchIt = false;
}
}else{
createPage(this.currentPage);
}
}
}
}
Ist it possible to have more than 2 elements per column?
Hi,Adam.It is really amazing.Thanks for sharing! Can I use it in my new project?
Ulas code above did not work for me, but the following code works:
package com.adambergman.ui.layout
{
/*************************************************************************
Copyright 2009 Adam Bergman (adambergman.com).
You are free to use this code in any personal or commercial projects.
I only ask that if you intend to use this code in any commercial project,
please contact me via adambergman.com and let me know.
*************************************************************************/
import flash.display.DisplayObject;
import mx.core.ILayoutElement;
import spark.components.supportClasses.GroupBase;
import spark.layouts.supportClasses.LayoutBase;
import gs.TweenLite;
import gs.easing.Bounce;
import gs.easing.Cubic;
import gs.easing.Elastic;
import gs.easing.Linear;
import gs.easing.Quad;
public class VPagedLayout extends LayoutBase
{
[Bindable] public static var EASING_BOUNCE:String = “bounce”;
[Bindable] public static var EASING_ELASTIC:String = “elastic”;
[Bindable] public static var EASING_DEFAULT:String = “”;
[Bindable] public static var EASING_CUBIC:String = “cubic”;
// Private variables for our properties
private var _currentPage:Number = 1;
private var _requestedPage:Number = 1;
private var _totalPages:Number = 1;
private var _itemPadding:Number = 10;
private var _isAnimating:Boolean = false;
private var _animationDuration:Number = 1;
private var _useAnimation:Boolean = true;
private var _easingFunction:Function = Quad.easeOut;
private var _easingType:String = EASING_DEFAULT;
public function get easingType():String
{
return _easingType;
}
public function set easingType(v:String):void
{
switch(v)
{
case EASING_BOUNCE: this.easingFunction = Bounce.easeOut; break;
case EASING_ELASTIC: this.easingFunction = Elastic.easeOut; break;
case EASING_DEFAULT: this.easingFunction = Quad.easeOut; break;
case EASING_CUBIC: this.easingFunction = Cubic.easeOut; break;
default: this.easingFunction = Linear.easeNone; break;
}
_easingType = v;
}
private function get easingFunction():Function
{
return _easingFunction;
}
private function set easingFunction(v:Function):void
{
_easingFunction = v;
}
public function VPagedLayout()
{
super();
this.clipAndEnableScrolling = true;
}
[Bindable]
[Bindable] public function get useAnimation():Boolean
{
return _useAnimation;
}
public function set useAnimation(v:Boolean):void
{
_useAnimation = v;
}
[Bindable] public function get animationDuration():Number
{
return _animationDuration;
}
public function set animationDuration(v:Number):void
{
_animationDuration = v;
}
[Bindable] public function get isAnimating():Boolean
{
return _isAnimating;
}
private function set isAnimating(v:Boolean):void
{
_isAnimating = v;
}
[Bindable] public function get requestedPage():Number
{
return _requestedPage;
}
public function set requestedPage(v:Number):void
{
_requestedPage = v;
}
public function get itemPadding():Number
{
return _itemPadding;
}
public function set itemPadding(v:Number):void
{
_itemPadding = v;
var layoutTarget:GroupBase = target;
if (!layoutTarget)
return;
layoutTarget.invalidateDisplayList();
}
[Bindable] public function get totalPages():Number
{
return _totalPages;
}
private function set totalPages(v:Number):void
{
_totalPages = v;
}
[Bindable] public function get currentPage():Number
{
return _currentPage;
}
private function set currentPage(v:Number):void
{
_currentPage = v;
}
private var switchIt:Boolean = false;
// Move to the Next Page
public function next():void
{
if(this.isAnimating){ return; }
updatePageCount();
if(this.currentPage + 1 > this.totalPages)
{
this.requestedPage = 1;
switchIt = true;
}else{
this.requestedPage++;
}
var layoutTarget:GroupBase = target;
if (!layoutTarget)
return;
layoutTarget.invalidateDisplayList();
}
// Move to the Previous Page
public function previous():void
{
if(this.isAnimating){ return; }
updatePageCount();
if(this.currentPage – 1 == 0)
{
this.requestedPage = this.totalPages;
switchIt = true;
}else{
this.requestedPage–;
}
var layoutTarget:GroupBase = target;
if (!layoutTarget)
return;
layoutTarget.invalidateDisplayList();
}
// Examine the height of the container and all layout elements
// and determine the number of pages
private function updatePageCount():void
{
var layoutTarget:GroupBase = target;
if (!layoutTarget)
return;
var element:ILayoutElement;
var pageCount:Number = 1;
var totalHeight:Number = 0;
for (var index:int = 0; index layoutTarget.height)
{
pageCount++;
totalHeight = element.getLayoutBoundsHeight() + _itemPadding;
}
}
this.totalPages = pageCount;
}
// Returns true if the given “testElement” exists on the the given page “pageNumber”
private function isElementOnPage(testElement:ILayoutElement, pageNumber:Number):Boolean
{
var layoutTarget:GroupBase = target;
if (!layoutTarget)
return false;
var element:ILayoutElement;
var pageCount:Number = 1;
var totalHeight:Number = 0;
var layoutHeight:Number = layoutTarget.height + layoutTarget.getStyle(“paddingTop”) + layoutTarget.getStyle(“paddingBottom”);
for (var index:int = 0; index layoutTarget.height)
{
pageCount++;
totalHeight = element.getLayoutBoundsHeight() + _itemPadding;
}
if(element == testElement)
{
if(pageCount == pageNumber){ return true; }else{ return false; }
}
}
return false;
}
// This is the method that actually moves layout elements into position
private function createPage(pageNum:Number, offset:Number=0, hideItems:Boolean=true):void
{
// hack to check if the current Page Number requested is less than or equal to
// the total amount of available pages (in case of window or component resize)
if(pageNum > this.totalPages){ pageNum = this.totalPages; this.currentPage = pageNum; }
var layoutTarget:GroupBase = target;
if (!layoutTarget)
return;
var element:ILayoutElement;
var totalHeight:Number = 5;
for (var index:int = 0; index this.currentPage)
{
offsetMultiplier = -1;
}else{
offsetMultiplier = 1;
}
if(switchIt)
{
offsetMultiplier *= -1;
}
var offset:Number = (layoutTarget.height + 10) * offsetMultiplier;
createPage(this.currentPage);
createPage(this.requestedPage, offset, false);
var element:ILayoutElement;
for (var index:int = 0; index < target.numElements; index++)
{
element = target.getElementAt(index);
if( !element.includeInLayout )
continue;
if(isElementOnPage(element, this.currentPage) || isElementOnPage(element, this.requestedPage))
{
TweenLite.to(DisplayObject(element), this.animationDuration, {y: DisplayObject(element).y + (offset), ease: this.easingFunction});
}
}
TweenLite.delayedCall(this.animationDuration, animationDone);
}
private function animationDone():void
{
this.isAnimating = false;
this.currentPage = this.requestedPage;
}
// Override the updateDisplayList method and call our sub methods
override public function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
var layoutTarget:GroupBase = target;
if (!layoutTarget)
return;
updatePageCount();
if(this.totalPages < 1){ return; }
if(this.requestedPage != this.currentPage && this.useAnimation)
{
if(!this.isAnimating)
{
animatePages(this.switchIt);
this.switchIt = false;
}
}else{
createPage(this.currentPage);
}
}
}
}
import gs.easing.Bounce;
import gs.easing.Cubic;
import gs.easing.Elastic;
import gs.easing.Linear;
import gs.easing.Quad;