In the good old mx world, the alternatingItemColors extend all the way to the height of the list. In the spark list, it stops where the data ends. Does anyone know if there is an easy way to make the Spark list behave similar to the mx list? See the image below. there is white space below the list, this does not happen in the mx list.
The difficulty will depend on how flexible your solution must be, but in general I think your best bet would be to create a custom skin for this List.
Create a custom skin
You can start by copying the Spark List skin (spark.skins.spark.ListSkin) and making some minor modifications to it:
<!-- AlternatingColorListSkin.mxml -->
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark">
<fx:Metadata>
[HostComponent("spark.components.List")]
</fx:Metadata>
<s:states>
<s:State name="normal"/>
<s:State name="disabled"/>
</s:states>
<s:Rect id="background" left="0" right="0" top="0" bottom="0">
<s:fill>
<s:SolidColor id="bgFill" color="0xEEEEEE"/>
</s:fill>
<s:stroke>
<s:SolidColorStroke id="borderStroke" weight="1"/>
</s:stroke>
</s:Rect>
<s:Group id="oddRowGroup" left="1" right="1" top="1" bottom="1" clipAndEnableScrolling="true"/>
<s:Scroller id="scroller" left="0" top="0" right="0" bottom="0" minViewportInset="1" hasFocusableChildren="false">
<s:DataGroup id="dataGroup" itemRenderer="spark.skins.spark.DefaultItemRenderer">
<s:layout>
<s:VerticalLayout id="dataLayout" gap="0" horizontalAlign="contentJustify" requestedMinRowCount="5"/>
</s:layout>
</s:DataGroup>
</s:Scroller>
</s:Skin>
In the code above I left out the portions of ListSkin that are irrelevant to this answer and I made these modifications:
the background fill color is the color of the even rows (instead of white by default)
I added 'oddRowGroup' to which we'll be adding some darker rectangles
I gave the DataGroup's layout an id (dataLayout) so that we can easily access it later
Add the odd row backgrounds
We'll now be adding the odd row backgrounds to oddRowGroup dynamically. Note that oddRowGroup's left/right/top/bottom is 1 so that the row backgrounds will not overlap the main background's border. It also has clipAndEnableScrolling set to true which will cause any possible overlap of the last odd row to be clipped.
Now add the following code in a script block in the skin:
private var previousHeight:Number = NaN;
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
super.updateDisplayList(unscaledWidth, unscaledHeight);
if (previousHeight != unscaledHeight) refreshBackground(unscaledHeight);
previousHeight = unscaledHeight;
}
private function refreshBackground(height:Number):void {
var rowHeight:Number = dataLayout.rowHeight;
var maxRows:int = height / rowHeight / 2;
oddRowGroup.removeAllElements();
for (var i:int = 0; i < maxRows; i++) {
var row:FilledElement = new Rect();
row.left = row.right = 0;
row.height = rowHeight;
row.top = (i * 2 + 1) * rowHeight;
row.fill = new SolidColor(0xdfdfdf);
oddRowGroup.addElement(row);
}
}
What happens here?
In updateDisplayList (which is called amongst others each time the List is resized), we check whether the height has changed. If it has, we refresh the odd row backgrounds.
We do so by removing all backgrounds that were drawn for the previous height.
We get the rowHeight from the DataGroup's layout, and we calculate the total number of required backgrounds. Then we create some Rects based on these numbers and we add them to oddRowGroup.
Limitations
The solution as it is, has some limitations. For instance the alternating colors are hardcoded (which can easily fixed by getting the right colors using getStyle()); it assumes there will be no override of the default layout (which is a VerticalLayout); it assumes variableRowHeight is off; and probably some more. But I think it's a good starting point.
As stated, I'm trying to obtain column headers consisting of an icon and wrappable text in a flex AdvancedDataGrid.
(EDIT: I forgot to mention an important part of the context: the columns are added dynamically, in actionscript. This apparently changes the behavior.)
I've tried using a custom mxml headerRenderer, like so:
<mx:headerRenderer>
<fx:Component>
<mx:HBox width="100%"
height="100%"
verticalAlign="middle">
<mx:Image source="<image_url>"
width="10%"
height="100%"/>
<mx:Text text="{data.headerText}"
width="90%"
height="100%"/>
</mx:HBox>
</fx:Component>
</mx:headerRenderer>
but for some reason, the text here is truncated instead of wrapped (it works outside of a renderer).
I've also tried creating a subclass of AdvancedDataGridHeaderRenderer and overriding createChildren to add the icon:
override protected function createChildren():void
{
var icon:Image = new Image();
icon.source = <image_url>;
icon.width = 16;
icon.height = 16;
addChild(icon);
super.createChildren();
}
but then, the icon and the text get superimposed.
I'm out of ideas on this. Anyone else?
It worked for me when I removed the height="100%" attribute from mx:Text in your headerRenderer.
UPDATE: it only works like this when I manually stretch the AdvancedDataGrid component. I'll look into how to make it work unconditionally.
When the height of the Text component was set to 100%, it was constrained to its parent HBox's height. Therefore when a word was wrapped and moved to the next line, it wasn't visible because the height of the Text component didn't allow for it to be visible.
If you remove this constraint, Text component's height will be determined dynamically based on its contents, as will headerRenderer's. Also add minHeight to your Text so that it is visible when it's loaded.
Here's the code (I also removed scrollbars because they were showing during resize):
<mx:headerRenderer>
<fx:Component>
<mx:HBox width="100%"
height="100%"
verticalAlign="middle"
horizontalScrollPolicy="off"
verticalScrollPolicy="off">
<mx:Image source="<image_url>"
width="10%"
height="100%"/>
<mx:Text text="{data.headerText}"
width="90%"
minHeight="20"/>
</mx:HBox>
</fx:Component>
</mx:headerRenderer>
In case anyone is interested in how to do this with dynamically created columns, a combination of Hunternif's code for the renderer and some added code on column creation worked for me:
The columns need to have fixed widths and need to be invalidated to inform the AdvancedDataGrid that it needs to rerender:
var cols:Array = [];
for each (...) {
var column:AdvancedDataGridColumn = new AdvancedDataGridColumn();
...
// Fix the width of created columns
column.width = 150;
cols.push(column);
}
grid.columns = cols;
// Invalidate columns so that sizes are recalculated
grid.mx_internal::columnsInvalid = true;
// Take changes into account
grid.validateNow();
I'm experiencing a very strange behavior with a spark list with TileLayout placed inside a scroller. Basically, I want to have a title area above my list that scrolls away when the user scrolls down the list. To do this I put the title and the list inside a Group and wrapped the group inside a scroller with width and height = 100%. I also set the verticalScrollPolicy to off on the list to make sure everything scrolls together.
The problem is that if the list has the default VerticalLayout everything works fine but if you assign a TileLayout to the same list it only partially renders the items (about 30 items when testing on iPhone 4).
Here's the fully functioning code. Try it like this first, then try removing the <s:layout> part to confirm that it works well with VerticalLayout:
<?xml version="1.0" encoding="utf-8"?>
<s:View
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
>
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
public var myAC:ArrayCollection = new ArrayCollection([
"01","02","03","04","05","06","07","08","09","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31","32","33","34","35","36","37","38","39","40","41","42","43","44","45","46","47","48","49","50","51","52","53","54","55","56","57","58","59","60","61","62","63","64","65","66","67","68","69","70","71","72","73","74","75","76","77","78","79","80","81","82","83","84","85","86","87","88","89","90","91","92","93","94","95","96","97","98","99","100"
]);
]]>
</fx:Script>
<s:Scroller width="100%" height="100%">
<s:VGroup>
<s:Label text="TITLE" width="100%" height="200" backgroundColor="#333333" color="#EEEEEE"/>
<s:List
id="list"
width="100%"
verticalScrollPolicy="off"
dataProvider="{myAC}" >
<s:layout>
<s:TileLayout
columnWidth="200"
rowHeight="200"
useVirtualLayout="true" />
</s:layout>
</s:List>
</s:VGroup>
</s:Scroller>
</s:View>
What am I doing wrong? Or is this a bug?
You need to calculate and set the height of the list. You can easily calculate it by figuring out the number of rows times the height of a row as below:
private function listHeight():Number {
// In this example you had 3 items to a row on the iPhone4 emulator
var numRows:Number = Math.ceil(myAC.length / 3);
// The height of the row (200) plus the gap between rows (6)
var rowHeight:Number = 200 + 6;
return numRows * rowHeight;
}
This is not a perfect solution, especially if you are targeting multiple screen densities (as the number of items per row will differ from device to device), but it might get you on the right track.
A better solution would be to extend the list component in an ActionScript class and add a header which you can set.
I am adding a flex image component to a mx:canvas component with a fairly large image. I have the horizontal and vertical scroll policies set to "on", but when I add the image to the canvas, it doesn't expand to show the whole image (the scrollbars aren't activated).
Anybody else have this issue.
The code is pretty straightforward:
<mx:Canvas id="myCanvas" minWidth="0" minHeight="0" horizontalScrollPolicy="on" verticalScrollPolicy="on">
</mx:Canvas>
and the script adding the image
var newImg:Image = new Image();
newImg.source = $value.sourceImg;
newImg.x = $value.positionX;
newImg.y = $value.positionY;
newImg.scaleX = $value.scaleX * _scaleRatio ;
newImg.scaleY = $value.scaleY * _scaleRatio;
newImg.rotation = $value.rotation;
myCanvas.addChild(newImg);
Ok, So I had to use clipCOntent = true. I had clipContent="false", I thought the meant that it would clip the image and anything outside the bounds could just be scrolled, buut it actually just clips it and doesn't offer a scroll.
It would be helpful if you posted your code, but without seeing it I would recommend setting minWidth="0" on the canvas. This is an old trick to force a re-measure of the canvas so it shows the scroll bars properly. Hope that helps.
Try using canvas.rawChildren.addChild(img) instead of canvas.addChild(img). That's worked for me before, otherwise you'll need to hook into the Flex component measuring system - but this is very seldomly necessary.
Cheers
Create a first canvas (viewCanvas) and place another canvas inside it (imageCanvas).
Set the scroll policies on the imageCanvas to 'off'. This should work, and the viewCanvas should have scrollbars. Note that no width/height are specified on the imageCanvas or image in code below
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Canvas id="viewCanvas" x="95" y="65" width="169" height="159">
<mx:Canvas id="imageCanvas" x="0" y="0" horizontalScrollPolicy="off" verticalScrollPolicy="off">
<mx:Image x="0" y="0">
<mx:source>http://www.beach-holiday.cn/beach-holiday/pics/2009/09/florida-fort-lauderdale.jpg</mx:source>
</mx:Image>
</mx:Canvas>
</mx:Canvas>
</mx:Application>
I'm trying to create a skin for a Flex 4 spark Panel that will create a border on the left/right side of the panel that matches the header of the Panel. Basically a silver frame all the way around the edge of the Panel similar to the old Flex 3 mx:Panel. I've been experimenting with a skin file all day and can't get anything to work. Any ideas?
I threw something together real fast. Create a Skin based on the existing PanelSkin. Then go find the group with id="contentGroup". Change it to something like this:
<s:Group width="100%" height="100%" minWidth="0" minHeight="0">
<s:Line stroke="{wrapperStroke}" top="0" left="0" bottom="{0}" yFrom="0" yTo="{this.height}" xFrom="0" xTo="0"/>
<s:Line stroke="{wrapperStroke}" top="0" right="0" bottom="{0}" yFrom="0" yTo="{this.height}" xFrom="0" xTo="0"/>
<s:Group id="contentGroup">
</s:Group>
</s:Group>
Then all you need to do is move your contentGroup away from the edges and give the stroke a color and weight. So, head to the updateDisplayList in the skin and add some code like:
wrapperStroke.color = 0xD9D9D9;
wrapperStroke.alpha = 1.0;
wrapperStroke.caps = CapsStyle.SQUARE;
wrapperStroke.weight = 3;
contentGroup.top = 3;
contentGroup.bottom = 3;
contentGroup.left = 3;
contentGroup.right = 3;
Also just put your wrapperStroke in a Declarations area like so:
<fx:Declarations>
<s:SolidColorStroke id="wrapperStroke" />
</fx:Declarations>
I hardcoded everything but you should be able to easily get them as Styles. Your PanelSkin should now look like this. I increased the weight of the stroke so it's easier to see.