扩展QNX TileList:Liquid Tile List





5.00/5 (2投票s)
扩展TileList和AlternatingCellRenderer QNX
引言
在我尝试了用于 PlayBook 开发的 QNX UI 库中可用的列表组件后,我的第一个问题是:如何自定义这些组件的外观和功能?
默认情况下,TileList 看起来是这样的
您使用 dataProvider
属性来设置数据,其默认的渲染器(CellRenderer)期望数据是一个具有名为 label
的 String
属性的对象。这个属性将用于显示每个图块的文本。您的控件可以通过 columnCount
属性控制图块的数量,并且可以通过 columnWidth
和 rowHeight
属性设置图块的宽度和高度。您可以使用 cellPadding
属性来控制图块之间的间距。
这很棒。但如果您想显示更复杂的数据呢?比如说,您想包含一张图片、一个标签和一些图形?显然,您需要扩展默认的渲染器来实现这一点。这是我的想法:
其次,我希望 TileList
能够根据三个输入数据动态计算布局的列数:TileList
的宽度、cellPadding
值以及图块的大小。因此,在创建组件时,我没有硬编码 columnCount
属性,而是设置了图块的大小(columnWidth
和 rowHeight
)以及 TileList
的 width
,然后基于这些,TileList
将计算出给定宽度可以容纳多少列,并相应地设置 columnCount
属性。这里有一个例子(在第一张图中,图块大小设置为 130 像素,在第二张图中设置为 250 像素)
这意味着我需要扩展 TileList
组件来添加此行为。但是,第一步是创建一个自定义的 CellRenderer
,以便我能够显示图片、标签和圆角灰色方块。幸运的是,我的同事、传播大使 Renaun Erickson 创建了一个示例,它非常接近我需要的东西。
为 TileList 创建自定义 CellRenderer
Renaun 的示例非常接近我想达到的目标。我需要修改它的原因是我想要不同的组件排列方式,更重要的是,我想将图块的宽度和高度从 TileList
推送过来——我不想在 CellRenderer
类中硬编码这些值。
现在,让我们退一步,谈谈 CellRenderer
组件的生命周期。列表组件使用虚拟化;这意味着列表将创建有限数量的 CellRenderer
组件,并在您上下滚动列表时重复使用它们。每次重用 CellRenderer
实例时,TileList
组件都会通过 CellRenderer
的 data 属性推送需要由该单元格显示的新数据。
接下来,每次更改 columnWidth
/rowHeight
属性时,TileList
将遍历所有 CellRender
实例并调用 setSize()
方法来推送新值。
了解了这一点,就很清楚我需要做什么来创建我的自定义 CellRenderer
:覆盖 data()
和 setSize()
方法,并添加我需要的部分(image
、rectangle
、label
)。这是部分代码(完整代码请访问下载部分并获取项目源文件)
public class PictureCellRenderer extends AlternatingCellRenderer {
/**
* Skin parts used to render the data
*/
protected var img:Image;
protected var lbl:Label;
protected var bg:Sprite;
protected var format:TextFormat;
public function PictureCellRenderer() {
super();
// hide the built in label
label.visible = false;
createUI();
}
/**
* setSize() is called everytime the tiles size are changed
* this is where we add our method layout() to reposition/redraw
* various parts of the cell renderer.
*/
override public function setSize(w:Number, h:Number):void {
super.setSize(w, h);
//we want to draw the skin parts only when the
//actual width/height are set
//this method is called first for the default sizes and then for
//for the user preferred sizes
if (stage) {
layout();
}
}
/**
* Updates the text and image everytime a new data is set
* for this renderer instance.
*/
override public function set data(value:Object):void {
super.data = value;
//Update the image source and text if there is valid data.
if (value) {
img.setImage(value.img);
lbl.text = value.label;
}
}
/**
* Create all the cell renderer components just once
*/
private function createUI():void {
img = new Image();
bg = new Sprite();
lbl = new Label();
//...
//code to position the img, Sprite, and label
//...
addChild(img);
addChild(bg);
addChild(lbl);
}
/**
* Draws the rectangle used as background
* for the label
*/
private function drawBg():void {
//code to redraw the gray rounded rectangular
}
/**
* Reposition/redraw the renderer parts
* everytime the tile size is changed
*/
private function layout():void {
lbl.y = height - 20;
lbl.width = width - 10;
drawBg();
onComplete(new Event(Event.COMPLETE));
}
/**
* Resize the image once the bits were loaded
*/
private function onComplete(e:Event):void {
img.setSize(width - 20, height - 20);
}
}
关于上面代码的一些说明
- 用于显示数据的组件(
Image
、Sprite
和Label
)在构造函数中调用的createUI()
方法中创建——它们只创建一次。 - 加载图像时,您必须监听
Event.COMPLETE
事件,如果您想将宽度和高度设置为与图像大小不同的值。 - 每次调用
setSize()
方法时,都会执行layout()
函数;因此,Image
、Sprite
和Label
组件会被重新定位或调整大小,以使用新的可用宽度和高度。 - 在构造函数中,我将父类的内置标签组件设为不可见。我试图重用它而不是创建一个新的标签,但我找不到改变其位置或格式的方法。我不确定这是一个 bug,还是我的方法有问题。
- 我扩展了
AlternatingCellRenderer
而不是CellRender
,只是为了让相邻单元格具有不同的背景。如果您扩展后者,代码也应该可以工作。 - 您传递给此渲染器的数据必须是一个对象,该对象具有用于文本的
label
属性和用于Image
URL 的img
属性(两者都必须是String
)。 - 此渲染器使用 ImageCache 来缓存图像。根据您的应用程序,您必须考虑缓存多少图像,以免占用过多系统内存。
扩展 TileList 组件以支持 Liquid Layout
有了自定义 CellRenderer
,一半以上的工作就完成了——至少我一开始是这么认为的 :)。那么剩下的就是实现 TileList
能够自动决定给定宽度能容纳多少列,并且每次组件的宽度或图块的宽度发生变化时,重新计算列数并更新其布局。将此行为集成到我的自定义 TileList
中意味着:
- 当显示方向改变时,我无需执行任何操作——
TileList
将自动更改列数以适应新的宽度和高度。 - 我可以在运行时控制图块的大小;例如,用户可以使用滑块放大或缩小。
我有两个选项可以实现此行为:
- 创建一个执行所有数学计算以确定新的
columnCount
的方法。 - 或者扩展
TileList
组件,并将此行为构建到组件本身中。
我选择了第二个选项,在解释我如何做之前,这是 LiquidTileList
组件的代码。
public class LiquidTileList extends TileList {
/**
* if true, when changing the size of the tiles
* scrolls the list so the first tile from the previous
* state is still visible
*/
public var keepVisibleItem:Boolean;
public function LiquidTileList() {
super();
setSkin(PictureCellRenderer);
}
/**
* Overriding the draw method to inject
* our method of calculating the number of
* tiles that fit the screen
* This method is called every time the width/height
* is changed
*/
override protected function draw():void {
var i:int;
if (keepVisibleItem && firstVisibleItem)
i = firstVisibleItem.index;
scrollIndexVisible(0, 0);
calculateColumns();
super.draw();
if (keepVisibleItem && i)
scrollIndexVisible(i, 0);
}
/**
* Calculates the number of tiles that fit
* the width
*/
private function calculateColumns():void {
var columnNumber:int = Math.floor( (width - (cellPadding * columnCount -1) )
/ columnWidth);
if (columnNumber != columnCount)
columnCount = columnNumber;
}
}
在构造函数中,我将 PictureCellRenderer
设置为默认皮肤。接下来,我覆盖了 draw()
方法。此方法在每次更改列表的宽度和高度,或更改图块大小(columnWidth
、rowHeight
)时调用;首先,我使用新的尺寸(通过调用 calculateColumns()
方法)计算列数,然后调用父类的 draw()
方法,使用新的 columnCount
值重新进行布局。
如果您想知道 scrollIndexVisible()
调用有什么作用,那么这就是难点。我认为 TileList
的虚拟化引擎存在一个 bug,因为当我更改图块大小时,有时会出现丢失项或无法滚动。所以这是我找到的解决此问题的唯一方法。其次,我希望将第一个可见元素保留在视图中。例如,假设您有一个大列表,您滚动了一点,然后决定将图块的大小增加三倍。我认为用户会期望看到之前视图中的第一个项目。所以这就是我添加一个名为 keepVisibleItem
的 public
属性的原因。
在我的测试中,应用 scrollIndexVisible
修复后,组件工作正常。但是,如果您想使用滑块更改图块大小,并且实时应用新值(通过监听 SliderEvent.MOVE
事件应用新值),那么最好将 keepVisibleItem
设置为 false
。
这是如何使用此自定义组件的代码片段。
peopleList = new LiquidTileList();
peopleList.keepVisibleItem = true;
peopleList.dataProvider = getPeople();
peopleList.scrollDirection = ScrollDirection.VERTICAL;
peopleList.size = 100;
peopleList.sizeUnit = SizeUnit.PERCENT;
peopleList.sizeMode = SizeMode.BOTH;
//provide a default number of columns;
//this can be override by LiquidTileList so it fill all
//the available width
peopleList.columnCount = 5;
peopleList.columnWidth = 150;
peopleList.rowHeight = 150;
peopleList.cellPadding = 5;
peopleList.selectionMode = ListSelectionMode.SINGLE;
slider = new Slider();
slider.minimum = 130; //min value
slider.maximum = 250; //max value
slider.value = 150; //default value
slider.addEventListener(SliderEvent.END, onSliderMove);
slider.addEventListener(SliderEvent.MOVE, onSliderMove);
...
/**
* Slider event changing the size of the tiles
*/
private function onSliderMove(e:SliderEvent):void {
peopleList.columnWidth = e.value;
peopleList.rowHeight = e.value;
}
下载代码
您可以从. 下载整个项目。仔细查看,如果您发现 bug 或有更好的想法,请发表评论。 :)
历史
- 2011 年 4 月 18 日:初始版本