如图所示(这是由 PhotoShop 绘制的,尚未实现),我想实现一个List
这样的。它有不同的列数,比如第一行只有一个项目,其他有两个项目。我尝试使用itemRendererFunction
来检测不同的项目(第一行视为渲染器A,其他视为另一个渲染器B),但它不起作用。
2 回答
这个问题最干净的解决方案是创建自定义布局(我们在评论中讨论了 Romi 的解决方案最终会导致太多问题)。然而,这通常不是一件容易的事。
我将为您提供此自定义布局可能是什么样子的粗略草图,因此您可以将其作为起点来创建一个完全符合您需要的布局。要创建自定义布局,您必须继承BaseLayout
并覆盖并实现其updateDisplayList
和measure
方法。
为了让事情变得更容易(并且为了不在这里转储 500 行代码),我在这个例子中使用了一些硬编码的变量。它假设总是有两列,第一项总是 200x200 像素,其他项目总是 100x100 像素。没有horizontalGap 或verticalGap。
结果当然是你可以只为这个特定的列表和这些特定的 ItemRenderers 使用这个自定义布局(就像现在一样)。如果您希望它更通用,则必须进行更多计算。
但现在代码:
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;
}
}
你可以像这样使用它:
<s:List dataProvider="{dp}" height="300">
<s:layout>
<l:MyCustomLayout />
</s:layout>
</s:List>
每当您想更改现有组件的已定义行为时,请始终先检查是否可以解决蒙皮问题。它是 i Flex 的一个非常强大的功能,也可以在这种情况下提供解决方案。
所以,让我们开始吧,假设您已经有了列表,您只需要创建一个自定义皮肤,它将数据提供者“拆分”为两部分,第一项和所有其他部分。所以,假设我们有这个初始设置:
<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}" />
如您所见,我们定义了一个自定义列表皮肤,它只是elementspark.skins.spark.ListSkin
的默认皮肤的副本。spark.components.List
在我们处理数据提供者逻辑之前,我们需要看看列表项是如何呈现的。这是通过使用DataGroup
添加到皮肤中的元素来完成的,如下所示:
<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>
这是我们必须进行更改的地方,以便让第一个元素以不同的方式呈现。我们需要做的,只是添加另一个 DataGroup,用于以自定义方式渲染第一个元素(这当然意味着使用自定义项渲染器)。现在,我们的滚动条看起来像这样:
<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>
请注意添加的“firstItemDataGroup”,以及它使用与默认 dataGroup 元素不同的项目渲染器这一事实。有了这个新容器,我们可以继续渲染元素。自定义皮肤需要重写父类的initializationComplete() 方法,如下所示:
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();
}
添加的只是“if”块和一个名为 dataGroupProvider 的私有变量。这是因为我们将在 updateDisplayList() 方法中将新的 dataProvider(从第二个元素开始)设置为 dataGroup 元素。这是它的样子:
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);
}
总之,只需为我们的 List 元素创建一个自定义皮肤,我们就可以使用两个容器以不同于其他元素的方式呈现第一项。你不应该低估 Flex Skinning 的力量 :)
希望这可以帮助。祝你有美好的一天!