List with different column count - actionscript-3

As the pic show(This is drawn by PhotoShop, not implemented yet), I want to implemnt a List like this one. It has different column count, say the first row has only one item, and the others have two items . I tried to use itemRendererFunction to detect the different item(the first row treat as a rendererA, the others treat as another rendererB),but it didn't work.

The cleanest solution to this problem, is to create a custom layout (we've discussed in the comments how Romi's solution will eventually cause too many problems). However, this is usually not an easy thing to do.
I will give you a rough sketch of what this custom layout might look like, so you can use it as a starting point to create one that does exactly what you need.
To create a custom layout, you must subclass BaseLayout and override and implement its updateDisplayList and measure methods.
To make things easier (and in order not to dump 500 lines of code in here), I used some hardcoded variables for this example. It assumes there will always be two columns, the first item will always be 200x200 px, and the other items will always be 100x100 px. There is no horizontalGap or verticalGap.
The consequence is of course that you can use this custom layout (as it is now) only for this specific List and these specific ItemRenderers. If you want it to be more generic, you'll have to do a lot more calculations.
But now for the code:
public class MyCustomLayout extends LayoutBase {
//hardcoded variables
private var columnCount:int = 2;
private var bigTileWidth:Number = 200;
private var bigTileHeight:Number = 200;
private var smallTileWidth:Number = 100;
private var smallTileHeight:Number = 100;
override public function updateDisplayList(width:Number, height:Number):void {
var layoutTarget:GroupBase = target;
if (!layoutTarget) return;
var numElements:int = layoutTarget.numElements;
if (!numElements) return;
//position and size the first element
var el:ILayoutElement = useVirtualLayout ?
layoutTarget.getVirtualElementAt(0) : layoutTarget.getElementAt(0);
el.setLayoutBoundsSize(bigTileWidth, bigTileHeight);
el.setLayoutBoundsPosition(0, 0);
//position & size the other elements in 2 columns below the 1st element
for (var i:int=1; i<numElements; i++) {
var x:Number = smallTileWidth * ((i-1) % 2);
var y:Number = smallTileHeight * Math.floor((i-1) / 2) + bigTileHeight;
el = useVirtualLayout ?
layoutTarget.getVirtualElementAt(i) :
layoutTarget.getElementAt(i);
el.setLayoutBoundsSize(smallTileWidth, smallTileHeight);
el.setLayoutBoundsPosition(x, y);
}
//set the content size (necessary for scrolling)
layoutTarget.setContentSize(
layoutTarget.measuredWidth, layoutTarget.measuredHeight
);
}
override public function measure():void {
var layoutTarget:GroupBase = target;
if (!layoutTarget) return;
var rowCount:int = Math.ceil((layoutTarget.numElements - 1) / 2);
//measure the total width and height
layoutTarget.measuredWidth = layoutTarget.measuredMinWidth =
Math.max(smallTileWidth * columnCount, bigTileWidth);
layoutTarget.measuredHeight = layoutTarget.measuredMinHeight =
bigTileHeight + smallTileHeight * rowCount;
}
}
And you can use it like this:
<s:List dataProvider="{dp}" height="300">
<s:layout>
<l:MyCustomLayout />
</s:layout>
</s:List>

Whenever you want to change the defined behavior of an existing component, always check first if you can solve the problem with skinning. It is a really powerful feature i Flex, and can also provide a solution in this case.
So, let's begin, assuming you already have your List, you only need to create a custom skin which "splits" the data provider in two parts, the first item, and all the others. So, let's assume we have this initial setup:
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
private var c:ArrayCollection = new ArrayCollection([
"String 1",
"String 2",
"String 3",
"String 4",
"String 5",
"String 6",
"String 7",
"String 8",
"String 9",
"String 10",
"String 11",
"String 12",
"String 13",
"String 14",
"String 15"]);
]]>
</fx:Script>
<s:List skinClass="CustomSkinList" dataProvider="{c}" />
As you can see, we define a custom list skin, which is just a copy of spark.skins.spark.ListSkin, the default skin for spark.components.List element.
Before we handle the data provider logic, we need to take a look at how the list items are rendered. This is done by using a DataGroup element, added to the skin, like so:
<s:Scroller left="0" top="0" right="0" bottom="0" id="scroller" minViewportInset="1" hasFocusableChildren="false">
<!--- #copy spark.components.SkinnableDataContainer#dataGroup -->
<s:DataGroup id="dataGroup" itemRenderer="spark.skins.spark.DefaultItemRenderer">
<s:layout>
<!--- The default layout is vertical and measures at least for 5 rows.
When switching to a different layout, HorizontalLayout for example,
make sure to adjust the minWidth, minHeight sizes of the skin -->
<s:VerticalLayout gap="0" horizontalAlign="contentJustify" requestedMinRowCount="5" />
</s:layout>
</s:DataGroup>
</s:Scroller>
Here is the place where we will have to make the changes, in order to get the first element to render differently. What we need to do, is just add another DataGroup, for rendering the first element in a custom way (this of course means using a custom item renderer). Now, our scroller looks like this:
<s:Scroller left="0"
top="0"
right="0"
bottom="0"
id="scroller"
minViewportInset="1"
hasFocusableChildren="false">
<!--- #copy spark.components.SkinnableDataContainer#dataGroup -->
<s:VGroup width="100%" height="100%">
<s:DataGroup id="firstItemDataGroup"
width="100%"
itemRenderer="CustomItemRenderer"
height="20">
<s:layout>
<s:VerticalLayout />
</s:layout>
</s:DataGroup>
<s:DataGroup id="dataGroup" itemRenderer="spark.skins.spark.DefaultItemRenderer">
<s:layout>
<!--- The default layout is vertical and measures at least for 5 rows.
When switching to a different layout, HorizontalLayout for example,
make sure to adjust the minWidth, minHeight sizes of the skin -->
<s:TileLayout horizontalAlign="center" requestedColumnCount="2" />
</s:layout>
</s:DataGroup>
</s:VGroup>
</s:Scroller>
Notice the 'firstItemDataGroup' addition, also the fact that it uses a different item renderer, than the default dataGroup element. With this new container in place we can proceed to render the elements. The custom skin need to override the parent initializationComplete() method, like so:
override protected function initializationComplete():void
{
useChromeColor = true;
if (hostComponent.dataProvider && hostComponent.dataProvider.length > 0)
{
var allItems:Array = hostComponent.dataProvider.toArray().concat();
firstItemDataGroup.dataProvider = new ArrayCollection([hostComponent.dataProvider.getItemAt(0)]);
var remainingItems:Array = allItems.concat().reverse();
remainingItems.pop();
var reversed:Array = remainingItems.reverse();
dataGroupProvider = new ArrayCollection(reversed);
}
super.initializationComplete();
}
What was added was just the 'if' block, and a private variable, named dataGroupProvider. This is because we will set the new dataProvider, the one starting from the second element, to the dataGroup element, in the updateDisplayList() method. Here is what it looks like:
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
if (getStyle("borderVisible") == true)
{
border.visible = true;
background.left = background.top = background.right = background.bottom = 1;
scroller.minViewportInset = 1;
}
else
{
border.visible = false;
background.left = background.top = background.right = background.bottom = 0;
scroller.minViewportInset = 0;
}
// Here we assign the new data provider to the dataGroup element
if (dataGroupProvider)
dataGroup.dataProvider = dataGroupProvider;
borderStroke.color = getStyle("borderColor");
borderStroke.alpha = getStyle("borderAlpha");
super.updateDisplayList(unscaledWidth, unscaledHeight);
}
In conclusion, just by creating a custom skin for our List element, we can use two containers for rendering the first item in a different way, from the rest of the elements. You shouldn't underestimate the power of Flex Skinning :)
Hope this helps. Have a great day!

Related

Keep specific Datagrid row always at the end - also with Sorting

I'm stuck on following matter:
I have a datagrid with 10 items... I also added a extra row (row number 11) where I show a total of prevouis fields... But I always want to keep this last row (the totals) on the last line of the Datagrid (so always on position 11).
This means that when the datagrid is sorted on a column, the last row also changes in position according to the value in the datafield. Is there any easy and straightforward way of preventing it to be sorted with the other columns so that the column with the totals is always placed last? Or what approach would I use best?
Thanks for any help!
Please find below code Hope this may help you, i tried some workaround to achieve what you are looking for i am not sure how feasible is this code:-
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600"
>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.events.FlexEvent;
[Bindable]
private var dpHierarchy:ArrayCollection= new ArrayCollection([
{name:"A", region: "Arizona", number:1},
{name:"B", region: "Arizona", number:2},
{name:"C", region: "California", number:3},
{name:"D", region: "California", number:4}
]);
private function creationComp():void
{
var totalValue:int = 0;
for(var i:int=0; i<dpHierarchy.length; i++)
{
totalValue = totalValue + dpHierarchy[i].number;
}
var obj:Object = new Object();
obj.name = "Total";
obj.region = "==";
obj.number = totalValue;
dpHierarchy.addItem(obj)
myADG.dataProvider = dpHierarchy;
}
private function sortHandler(obj1:Object, obj2:Object):int
{
var lastObj:Object = dpHierarchy.getItemAt(dpHierarchy.length-1);
if(lastObj.number == obj1.number || lastObj.number == obj2.number)
return 0;
if(obj1.number < obj2.number) {
return -1;
} else if(obj1 == obj2) {
return 0;
}
return 1;
}
]]>
</fx:Script>
<mx:AdvancedDataGrid id="myADG" x="50" y="50"
width="400" height="300"
variableRowHeight="true" creationComplete="creationComp()">
<mx:columns>
<mx:AdvancedDataGridColumn dataField="name" headerText="Name" sortCompareFunction="sortHandler"/>
<mx:AdvancedDataGridColumn dataField="region" headerText="Region" sortCompareFunction="sortHandler"/>
<mx:AdvancedDataGridColumn dataField="number" headerText="Number" sortCompareFunction="sortHandler"/>
</mx:columns>
</mx:AdvancedDataGrid>
</s:Application>
I'd consider a few options in this order.
Put your summary data somewhere else. That's really not what a row in a datagrid was intended for.
Not let your users sort by clicking on the column name (sortableColumns='false') if you really don't want them to sort the data.
Create a custom sort for each column that ensures you always put your summary data last. Here's a decent tutorial: http://www.switchonthecode.com/tutorials/flex-datagrid-custom-sorting. I'd like to stress that this would be a high maintenance idea because you'd have to build out a custom sort for every column.

How to sort an ArrayCollection in Flex

I want to sort an Arraycollection by fieldName as ascending. Here's my code and I want to know whether it's right. Do you have any suggestions?
public static function arrayCollectionSort(ar:ArrayCollection, fieldName:String, isNumeric:Boolean):void
{var dataSortField:SortField = new SortField();
dataSortField.name = fieldName;
dataSortField.numeric = isNumeric;
var numericDataSort:Sort = new Sort();
numericDataSort.fields = [dataSortField];
arrCol.sort = numericDataSort;
arrCol.refresh();}
The code you have is correct, except for a type. arrCol should be ar. The code looks almost exactly like the code at the blog Flex Examples, which is also correct.
Just change is change arrCol to ar like below:
public static function arrayCollectionSort(ar:ArrayCollection, fieldName:String, isNumeric:Boolean):void
{
var dataSortField:SortField = new SortField();
dataSortField.name = fieldName;
dataSortField.numeric = isNumeric;
var numericDataSort:Sort = new Sort();
numericDataSort.fields = [dataSortField];
ar.sort = numericDataSort;
ar.refresh();
}
Not sure with numeric but otherwise everything else is correct.
Here is full example how to use sort in Array collection
http://blog.flexexamples.com/2007/08/05/sorting-an-arraycollection-using-the-sortfield-and-sort-classes/
Your code is fine, even so here are a couple of examples where a numeric and an alphabetical sort is applied on button clicks.
The alphabetical sort is a good example of sorting on 2 attributes. In this case, the primary sort is done on the 'firstname', the secondary sort is done on the 'lastname'.
The numerical sort is quite flexible, if you provide a boolean value of true for the numeric parameter of the sort field, the sort will cast the attribute to a number and sort by number. If you provide a boolean value of false, the built-in string compare function is used. Each of data items is cast to a String() function before the comparison. With the default value of null, the first data item is introspected to see if it is a number or string and the sort proceeds based on that introspection.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" minWidth="955" minHeight="600">
<mx:Button label="Sort by first then last name" click="sortItemsByName()"/>
<mx:Button label="Sort by number" click="sortItemsByNumber()"/>
<mx:DataGrid dataProvider="{items}"
width="300"
height="300">
<mx:columns>
<mx:DataGridColumn dataField="number"/>
<mx:DataGridColumn dataField="firstname"/>
<mx:DataGridColumn dataField="lastname"/>
</mx:columns>
</mx:DataGrid>
<mx:ArrayCollection id="items">
<mx:Object number="3" firstname="John" lastname="Brown"/>
<mx:Object number="1" firstname="Kate" lastname="Brown"/>
<mx:Object number="4" firstname="Jeremy" lastname="Ryan"/>
<mx:Object number="5" firstname="Joe" lastname="Wilson"/>
<mx:Object number="2" firstname="Greg" lastname="Walling"/>
</mx:ArrayCollection>
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.collections.Sort;
import mx.collections.SortField;
/**
* Sort the arraycollection by the firstname and then the last name
* */
private function sortItemsByName():void{
var srt:Sort = new Sort();
srt.fields = [new SortField("firstname"), new SortField("lastname")];
items.sort = srt;
items.refresh();
}
/**
* Sort the arraycollection numerically
* */
private function sortItemsByNumber():void{
var srt:Sort = new Sort();
srt.fields = [new SortField("number", true, false, true)];
items.sort = srt;
items.refresh();
}
]]>
</mx:Script>
</mx:Application>
Also here is the language reference for the sortField...
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/mx/collections/SortField.html
...and the Adobe livedocs reference for data providers and collections...
http://livedocs.adobe.com/flex/3/html/help.html?content=about_dataproviders_2.html
...and here is a good livedocs reference for sorting and filtering...
http://livedocs.adobe.com/flex/3/html/help.html?content=about_dataproviders_4.html

Flex multitouch path scale dynamic registration issue

Is there any (straightforward) way to solve the dynamic registration issue with multitouch scaling events in flex? I just can't wrap my head around this.
What I've got is (amongst some lines and labels) a path in a group that itself is wrapped in a scroller;
<s:Scroller id="scroller">
<s:Group id="scrollerContent">
<s:Path id="path">
<s:stroke>
<s:SolidColorStroke color="#ffffff" weight="2"/>
</s:stroke>
</s:Path>
</s:Group>
</s:Scroller>
What I'd like to do is to zoom in and out on the path (and the other stuff in the scrollerContent group), so in my creationComplete() method I added an eventListener to the scrollerContent group:
scrollerContent.addEventListener(TransformGestureEvent.GESTURE_ZOOM, zoomEvent);
Here is the code Christophe Coenraets provided for his chart example (which does in fact scale the path, based on x=0 though;
private function zoomEvent(e:TransformGestureEvent):void
{
zoom(e.scaleX, e.scaleY);
}
protected function zoom(scaleX:Number):void
{
var w:Number = path.width * scaleX;
if (scaleX>1)
path.width = w > width*5 ? width*5 : w;
else
{
path.width = w < width ? width : w;
if (path.x + path.width < width) path.x = width - path.width;
}
}
I'm aware of the DynamicRegistration class, but can't get it working properly, it still scales the path based on the x=0 point.
DynamicRegistration.scale(scrollerContent, new Point(e.localX, e.localY), scrollerContent.scaleX*= e.scaleX, scrollerContent.scaleY=1);
Any help regarding this would be much appreciated!
I used the DynamicRegistration class, and got it working like this, if you're still interested:
protected function onZoom(e:TransformGestureEvent, img:Image):void
{
DynamicRegistration.scale(img, new Point(e.localX, e.localY), img.scaleX*e.scaleX, img.scaleY*e.scaleY);
}
Or with the native flex method:
protected function onZoom(e:TransformGestureEvent, img:Image):void
{
img.transformAround(new Vector3D(e.localX, e.localY, 0), new Vector3D(img.scaleX*e.scaleX, img.scaleY*e.scaleY, 0));
}

Flex container with HTML style floating

I am using Flex 4 with Spark components to build a mobile application and I have a HGroup that I am using to contain all of my elements. When the screen loads it pulls in a small amount of text that will be displayed and loops through all the words to see if any of them are a keyword. While it is looping I am putting each word into its own label element and if the word is a keyword it changes a few styles and adds a click event to show a description about the word.
Everything runs fine but when everything is appended to the HGroup, there ends up being only one line and most of the text completely cut off because it will not wrap the content.
My Question is - Is there a way to set or extend the HGroup to allow content wrapping on its child elements?
Below are some code snippets of what I have:
MXML containers:
<s:VGroup id="answerData" width="580" height="700" horizontalAlign="center" paddingTop="5">
<s:HGroup id="theLabel" color="white" width="580" fontSize="25" paddingBottom="20" />
<s:HGroup id="theText" color="white" width="580" fontSize="25" maxWidth="580" />
</s:VGroup>
AS to create labels:
public static function setKeyWords(someText:String, theGroup:Group, theDictionary:Array, theView:Object):void {
theGroup.removeAllElements();
var textArray:Array = someText.split(' ');
for(var i:int = 0, l:int = textArray.length; i < l; i++) {
if(checkForWord(theDictionary, textArray[i].toString())) {
var theLink:Label = new Label();
theLink.text = textArray[i].toString();
theLink.setStyle("color", "0xFFFF00");
theLink.setStyle("fontWeight", "bold");
theLink.maxWidth = 580;
var tmpDescrip:String = theDescription;
theLink.addEventListener(MouseEvent.CLICK, function(evt:MouseEvent):void {
showToolTip(tmpDescrip, theView);
});
theGroup.addElement(theLink);
} else {
var someLabel:Label = new Label();
someLabel.maxWidth = 580;
someLabel.text = textArray[i].toString();
theGroup.addElement(someLabel);
}
}
}
The issue that I was having is, I had multiple lables in a VGroup and needed them to wrap instead of extending past the containers set width. I was trying to integrate keywords into a dynamic paragraph of text. I could not use mx:Text because I needed each word to be its own component that allowed custom styling plus a mouse click even if the word was a keyword. Also the label max lines solution would not work because I am dealing with multiple lables in a VGroup and the VGroup needed to wrap its children not the label tags. I also could not use a TileGroup because it does not look right breaking a paragraph into a table looking component where each word is in its own column/row.
The solution I used was to count each character in the label being generated and add it to a variable to determine when I need to create a new HGroup that holds the labels and sits in a VGroup. I had to do this because I cannot determine the labels width until it renders because it is generated dynamically. This could not be done because as its render point is too late for me to move everything because the user can see all of this happening which is definitely not the desired effect.
Below is the code I used to solve this issue incase anyone else runs into this issue:
public static function setKeyWords(someText:String, theGroup:Group, theDictionary:Array, theView:Object):void {
theGroup.removeAllElements();
var textArray:Array = someText.split(' ');
var theCount:int = 0;
var theHGroup:HGroup = new HGroup();
var breakNum:int = 40;
theHGroup.percentWidth = 100;
for(var i:int = 0, l:int = textArray.length; i < l; i++) {
theCount += textArray[i].toString().length;
if(theCount >= breakNum) {
theGroup.addElement(theHGroup);
theHGroup = new HGroup();
theHGroup.percentWidth = 100;
theCount = 0;
}
if(checkForWord(theDictionary, textArray[i].toString())) {
theCount += 1;
var theLink:Label = new Label();
theLink.text = textArray[i].toString();
theLink.setStyle("color", "0xFFFF00");
theLink.setStyle("fontWeight", "bold");
theLink.maxWidth = 580;
//theLink.includeInLayout = false;
var tmpDescrip:String = theDescription;
theLink.addEventListener(MouseEvent.CLICK, function(evt:MouseEvent):void {
showToolTip(tmpDescrip, theView, 'keywords');
});
theHGroup.addElement(theLink);
} else {
theCount += 1;
var someLabel:Label = new Label();
someLabel.maxWidth = 580;
someLabel.text = textArray[i].toString();
//someLabel.includeInLayout = false;
theHGroup.addElement(someLabel);
}
}
if(theCount > 0)
theGroup.addElement(theHGroup);
}
This may not be the most effecient way to do this but it does work and takes little time to execute on the Iphone which is what I was aiming for.
Not fully sure I understand your question.
If you mean the Label's are truncated, you might want to set a percentWidth and wrap Labels with maxDisplayedLines:
someLabel.maxDisplayedLines = 10;
If you mean you want columns and rows to your Group layout, use TileGroup / TileLayout.
If the children of your group contain composite content that must float, some kind of includeInLayout=false might help.
If you want text to show in block of several lines, use mx:Text with width set.
To display something above HGroup, easiest way is to leave HGroup alone and just make transparent container (Canvas) above it. There you'll be free to display anything (just do the math to position it correctly.)

Scroll to selected item in Flex 4 Spark List component

I'm setting selected element in s:List component with Actionscript, it works, but List doesn't scroll to selected item -- need to scroll with scrollbar or mouse. Is it possible to auto-scroll to selected item ? Thanks !
Try the s:List method ensureIndexIsVisible(index:int):void.
For Spark:
list.ensureIndexIsVisible(index);
This function will scroll to the top of the list in Flex 4+. It takes in account the height of the item, so it will work for lists with different items with different height.
private function scrollToIndex(list:List,index:int):void
{
if (!list.layout)
return;
var dataGroup:DataGroup = list.dataGroup;
var spDelta:Point = dataGroup.layout.getScrollPositionDeltaToElement(index);
if (spDelta)
{
dataGroup.horizontalScrollPosition += spDelta.x;
//move it to the top if the list has enough items
if(spDelta.y > 0)
{
var maxVSP:Number = dataGroup.contentHeight - dataGroup.height;
var itemBounds:Rectangle = list.layout.getElementBounds(index);
var newHeight:Number = dataGroup.verticalScrollPosition + spDelta.y
+ dataGroup.height - itemBounds.height;
dataGroup.verticalScrollPosition = Math.min(maxVSP, newHeight);
}
else
{
dataGroup.verticalScrollPosition += spDelta.y;
}
}
}
//try this
this.callLater(updateIndex);//where you want to set the selectedIndex
private function updateIndex():void
{
list.selectedIndex = newIndex;
list.ensureIndexIsVisible(newIndex);
}
In flex-3 there is a scrollToIndex method and hence you can call
list.scrollToIndex(list.selectedIndex);
I believe this should work in flex-4 too.
This worked for me. had to use the callLater.
list.selectedItem = "MyTestItem"; //or list.selectedIndex = 10;
this.callLater(updateIndex); //dispatch an update to list
private function updateIndex():void {
list.ensureIndexIsVisible(list.selectedIndex);
}
I saw this basic idea here...
http://arthurnn.com/blog/2011/01/12/coverflow-layout-for-flex-4/
public function scrollGroup( n : int ) : void
{
var scrollPoint : Point = theList.layout.getScrollPositionDeltaToElement( n );
var duration : Number = ( Math.max( scrollPoint.x, theList.layout.target.horizontalScrollPosition ) - Math.min( scrollPoint.x, theList.layout.target.horizontalScrollPosition )) * .01;
Tweener.addTween(theList.layout,{ horizontalScrollPosition: scrollPoint.x , time:duration});
}
protected function theList_caretChangeHandler(event:IndexChangeEvent):void
{
scrollGroup( event.newIndex );
event.target.invalidateDisplayList();
}
You'll probably want to access the List's scroller directly and do something like:
list.scroller.scrollRect.y = list.itemRenderer.height * index;
You can multiply the height of an element by its index and pass this value to:
yourListID.scroller.viewport.verticalScrollPosition
It is a bug - you can see the demonstration and a workaround at the https://issues.apache.org/jira/browse/FLEX-33660
This custom List component extension worked for me:
<s:List
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
valueCommit="callLater(ensureIndexIsVisible, [selectedIndex])">
</s:List>
I recently accomplished this in one of my projects by having a defined size for my items in the group..
<s:Scroller x="940" y="0" maxHeight="465" maxWidth="940" horizontalScrollPolicy="off" verticalScrollPolicy="off">
<s:HGroup id="tutPane" columnWidth="940" variableColumnWidth="false" gap="0" x="0" y="0">
</s:HGroup>
</s:Scroller>
Following this my button controls for manipulation worked by incrementing a private "targetindex" variable, then I called a checkAnimation function, which used the Animate class, in combo with a SimpleMotionPath and a comparison between tutpane.firstIndexInView and target index. This modified the "horizontalScrollPosition" of the group.
This allowed separate controls to essentially act as a scroll bar, but I had the requirement of sliding the control to view the selected item.. I believe this technique could work for automated selection of items as well