为BlackBerry PlayBook创建自定义列表皮肤





0/5 (0投票)
皮肤化列表与皮肤化简单的按钮或文本输入框有很大的不同
List
和 CellRenderer
中有很多我不太理解的东西。我希望有一天能和 QNX 团队坐下来,弄清楚如何正确地完成这件事,但目前,我找到了一个可行的办法,所以想与所有遇到困难的人分享。为 List
设置皮肤与为 Button
或 TextInput
框(我在这里的文章中讨论过)这类简单的组件设置皮肤有很大的不同。自定义列表外观和感觉的正确方法似乎是创建一个 CellRenderer
,它允许您访问列表的标签,然后通过创建专门为该 CellRenderer
设计的皮肤来替换图形。但是,我的特定代码不依赖于标签字段,所以我跳过了第一步,只创建了一个扩展 UISkin
并实现 ICellRenderer
的 CellRendererSkin
。通过实现 ICellRenderer
,我可以访问数据方法,但正如您将看到的,这产生了一些问题。
首先,这是我想创建的列表。它有一个图像和几行文字。您可以查看正常状态(白色)和选中状态(灰色)的外观。我想要更自定义的外观,所以在每个项目之间留了一些空间,并在内容周围绘制了一个带边框的圆角矩形。
要以这种方式为列表设置皮肤,最重要的仍然是 initializeStates()
方法。必须像我们为任何其他组件设置皮肤一样重写该方法,并且在那里我们调用 setSkinState
来将图形与状态关联起来。列表基本上有 8 种不同的 SkinStates
:SkinStates.UP
、SkinStates.UP_ODD
、SkinStates.DOWN
、SkinStates.FOCUS
、SkinStates.DISABLED
、SkinStates.SELECTED
、SkinStates.DOWN_SELECTED
和 SkinStates.DISABLED_SELECTED
。我不知道 FOCUS
是做什么的,我没有交替行,所以不关心 ODD,并且我决定我的 DOWN
、SELECTED
和 DOWN_SELECTED
保持相同。所以这是我的 initializeStates()
方法的代码。
override protected function initializeStates():void
{
_upSkin = new Sprite();
setSkinState(SkinStates.UP,_upSkin);
_downSkin = new Sprite();
setSkinState(SkinStates.DOWN,_downSkin);
setSkinState(SkinStates.DOWN_SELECTED,_downSkin);
setSkinState(SkinStates.SELECTED,_downSkin);
SkinStates
showSkin(_upSkin);
}
事情在这里变得有点棘手。在普通的皮肤设置中,我可以直接将图形添加到我的 sprite 中,然后相应地设置皮肤状态。但我的发现是,当调用 initializeStates()
方法时,组件的宽度和高度尚未设置。所以当我尝试绘制一个使用组件宽度/高度的矩形时,它会看起来被挤压。如果您知道确切的尺寸,可以硬编码这些值。但我想在不同大小的列表上使用它,所以我想使用这些动态值。
我发现,如果我重写 setState(state:String)
方法,我就可以在那里获取宽度/高度的值,然后根据正在设置的每个单元格的高度/宽度对应的状态绘制正确的图形。
override protected function setState(state:String):void
{
super.setState(state);
var matrix:Matrix = new Matrix();
matrix.createGradientBox(width,height,90/180*Math.PI);
if(state == SkinStates.UP)
{
_background.graphics.clear();
_background.graphics.beginGradientFill(GradientType.LINEAR,
[0xffffff,0xf2f2f2,0xffffff],[1,1,1],[0,127,255],matrix);
_background.graphics.lineStyle(2,0x221206);
_background.graphics.drawRoundRect(20,10,width-35,height-20,7,7);
_background.graphics.endFill();
}
if(state == SkinStates.DOWN ||
state == SkinStates.DOWN_SELECTED ||
state == SkinStates.SELECTED)
{
_background.graphics.clear();
_background.graphics.beginGradientFill(GradientType.LINEAR,
[0xaaaaaa,0xcfcfcf,0xaaaaaa],[1,1,1],[0,127,255],matrix);
_background.graphics.lineStyle(2,0x221206);
_background.graphics.drawRoundRect(20,10,width-35,height-20,7,7);
_background.graphics.endFill();
}
}
在这里,我遇到了另一个奇怪的问题。请注意,我不是将图形添加到状态,而是将它们添加到 _background
Sprite。我发现,当我尝试直接在皮肤 sprite 上绘制图形时,当状态更改时,它要么会覆盖我的其他内容而我无法恢复,要么根本不会显示图形。我仍然不完全确定这两种情况为什么会发生,而且我经历了太多次迭代,以至于我不记得导致这种情况的代码了。但我的发现是,如果我创建一个 background Sprite 并先将其添加到显示列表中,我可以根据状态修改它,它就会正确绘制。这也是为什么我有一个 _background.graphics.clear()
调用,因为 _background
Sprite 存在于每种状态中,只需在状态更改时重新绘制它。
下一步是添加所有内容到 cell renderer,然后在 cell renderer 消失时进行清理。所有 QNX 组件都有一个 onAdded()
和 onRemoved()
方法,当对象被添加到舞台或从舞台移除时会被调用。所以我只是重写了这些方法并添加了我的内容。
override protected function onAdded():void
{
super.onAdded();
addChild(_background);
addChild(_image);
addChild(_name);
addChild(_brewery);
addChild(_beerType);
addChild(_ratingText);
addChild(_avgRating);
}
override protected function onRemoved():void
{
super.onRemoved();
removeChild(_background);
removeChild(_image);
removeChild(_name);
removeChild(_brewery);
removeChild(_beerType);
removeChild(_ratingText);
removeChild(_avgRating);
}
还有一个最后的步骤。正如您将在下面的代码中看到的,我在构造函数中设置了标签和图像的大部分属性。但我没有设置任何动态数据。这是因为我很难找到何时可以访问 cell renderer 的 data
属性。在 init()
、onAdded()
或 initializeStates()
中没有数据,所以在这些方法中尝试设置动态数据会引发错误。我**可以**在 setState()
方法中访问它,但我发现当我在那里设置时,列表不会正确显示值。我最终发现这是因为列表是虚拟化的。setState()
方法在您首次滚动列表时不会被调用,因为状态尚未更改,只有数据更改了。所以当我滚动列表时,我看到了重复的值,直到我点击它并强制调用 setState()
,我才看到正确的值。
解决方案只是拥抱我正在实现 ICellRenderer
的事实,并在数据 setter 方法中设置所有数据。这为每个单元格正确设置了数据,并且根本不依赖于状态。我还必须在那里设置 Label
对象的宽度,以便文本不会被截断。
public function set data(data:Object):void
{
_beer = data;
_image.setImage(data.thumb);
_name.text = data.beerName;
_name.width = width-150;
_brewery.text = data.brewerName;
_brewery.width = width-150;
_beerType.text = data.styleName;
_beerType.width = width-150;
if(data.avgRating > 3)
{
_avgRatingFormat.color = 0x4c9d17;
} else if (data.avgRating < 1)
{
_avgRatingFormat.color = 0x9d1717;
}
_avgRating.text = data.avgRating;
_avgRating.format = _avgRatingFormat;
}
再次强调,我认为这可能不是理想的实现方式。**特别是**如果您有一个非常简单的标签,您可以只扩展 CellRenderer
并重写 draw()
和 drawLabel()
方法来实现您想要的功能。或者,如我上面所说,为该 CellRenderer
应用一个特殊的皮肤,该皮肤正确处理列表的所有状态。但这确实是我深入研究的“兔子洞”,我发现它很有用,因为我用一个类解决了我所有的难题。即使它是一个丑陋的类。这是完整的代码。
package com.pintley.components.listClasses
{
import flash.display.GradientType;
import flash.display.Sprite;
import flash.filters.DropShadowFilter;
import flash.geom.Matrix;
import flash.text.TextFormat;
import qnx.ui.display.Image;
import qnx.ui.listClasses.ICellRenderer;
import qnx.ui.skins.SkinStates;
import qnx.ui.skins.UISkin;
import qnx.ui.text.Label;
public class BeerCellRendererSkin extends UISkin implements ICellRenderer
{
protected var _beer:Object;
private var _row:int;
private var _column:int;
private var _section:int;
private var _index:int;
private var _yOffset:int = 12;
private var _xOffset:int = 35;
/**
* Skins
**/
protected var _upSkin:Sprite;
protected var _selectedSkin:Sprite;
protected var _downSkin:Sprite;
protected var _disabledSkin:Sprite;
/**
* Cell Renderer content
**/
// I use a background sprite because I want to
// make sure it's the lowest layer. Then I can just
// modify the lowest layer without overwriting the text.
protected var _background:Sprite;
protected var _name:Label;
protected var _brewery:Label;
protected var _beerType:Label;
protected var _ratingText:Label;
protected var _avgRating:Label;
protected var _image:Image;
/**
* TextFormats
**/
protected var _nameFormat:TextFormat;
protected var _breweryFormat:TextFormat;
protected var _beerTypeFormat:TextFormat;
protected var _ratingTextFormat:TextFormat;
protected var _avgRatingFormat:TextFormat;
public function BeerCellRendererSkin()
{
super();
/**
* TextFormats
**/
_nameFormat = new TextFormat();
_nameFormat.color = 0xbd5251;
_nameFormat.size = 16;
_nameFormat.bold = true;
_breweryFormat = new TextFormat();
_breweryFormat.color = 0x525252;
_breweryFormat.size = 14;
_breweryFormat.bold = true;
_beerTypeFormat = new TextFormat();
_beerTypeFormat.color = 0x525252;
_beerTypeFormat.size = 14;
_ratingTextFormat = new TextFormat();
_ratingTextFormat.color = 0x79523e;
_ratingTextFormat.size = 12;
_ratingTextFormat.bold = true;
_avgRatingFormat = new TextFormat();
_avgRatingFormat.color = 0x000000;
_avgRatingFormat.size = 14;
_avgRatingFormat.bold = true;
/**
* CellRenderer Content
**/
_image = new Image();
_image.x = _xOffset;
_image.y = 20;
_image.filters = [new DropShadowFilter(3,45,0x000000,.5,4,4,.5)];
_name = new Label();
_name.x = _xOffset+80;
_name.y = _yOffset;
_name.format = _nameFormat;
_brewery = new Label();
_brewery.x = _xOffset+80;
_brewery.y = _yOffset+20;
_brewery.format = _breweryFormat;
_beerType = new Label();
_beerType.x = _xOffset+80;
_beerType.y = _yOffset+35;
_beerType.format = _beerTypeFormat;
_ratingText = new Label();
_ratingText.x = _xOffset+80;
_ratingText.y = _yOffset+55;
_ratingText.format = _ratingTextFormat;
_ratingText.text = "AVG Rating";
_avgRating = new Label();
_avgRating.x = _xOffset + 150;
_avgRating.y = _yOffset+54;
_background = new Sprite();
}
/**
* Getters/Setters
**/
public function get data():Object
{
return _beer;
}
public function set data(data:Object):void
{
_beer = data;
// Set the text and images for the
// label after we get data from the list.
_image.setImage(data.thumb);
_name.text = data.beerName;
_name.width = width-150;
_brewery.text = data.brewerName;
_brewery.width = width-150;
_beerType.text = data.styleName;
_beerType.width = width-150;
if(data.avgRating > 3)
{
_avgRatingFormat.color = 0x4c9d17;
} else if (data.avgRating < 1)
{
_avgRatingFormat.color = 0x9d1717;
}
_avgRating.text = data.avgRating;
_avgRating.format = _avgRatingFormat;
}
public function get index():int
{
return _index;
}
public function set index(value:int):void
{
_index = value;
}
public function get row():int
{
return _row;
}
public function set row(value:int):void
{
_row = value;
}
public function get column():int
{
return _column;
}
public function set column(value:int):void
{
_column = value;
}
public function get section():int
{
return _section;
}
public function set section(section:int):void
{
_section = section;
}
public function get isHeader():Boolean
{
return false;
}
/**
* Overriden Functions
*/
override protected function initializeStates():void
{
// Set up the skin states
_upSkin = new Sprite();
setSkinState(SkinStates.UP,_upSkin);
_downSkin = new Sprite();
setSkinState(SkinStates.DOWN,_downSkin);
setSkinState(SkinStates.DOWN_SELECTED,_downSkin);
setSkinState(SkinStates.SELECTED,_downSkin);
showSkin(_upSkin);
}
override protected function setState(state:String):void
{
super.setState(state);
var matrix:Matrix = new Matrix();
matrix.createGradientBox(width,height,90/180*Math.PI);
// Check to see what state is being set and then draw
// the graphics on the background Sprite accordingly.
if(state == SkinStates.UP)
{
_background.graphics.clear();
_background.graphics.beginGradientFill(GradientType.LINEAR,
[0xffffff,0xf2f2f2,0xffffff],[1,1,1],[0,127,255],matrix);
_background.graphics.lineStyle(2,0x221206);
_background.graphics.drawRoundRect(20,10,width-35,height-20,7,7);
_background.graphics.endFill();
}
if(state == SkinStates.DOWN ||
state == SkinStates.DOWN_SELECTED ||
state == SkinStates.SELECTED)
{
_background.graphics.clear();
_background.graphics.beginGradientFill(GradientType.LINEAR,
[0xaaaaaa,0xcfcfcf,0xaaaaaa],[1,1,1],[0,127,255],matrix);
_background.graphics.lineStyle(2,0x221206);
_background.graphics.drawRoundRect(20,10,width-35,height-20,7,7);
_background.graphics.endFill();
}
}
override protected function onAdded():void
{
super.onAdded();
addChild(_background);
addChild(_image);
addChild(_name);
addChild(_brewery);
addChild(_beerType);
addChild(_ratingText);
addChild(_avgRating);
}
override protected function onRemoved():void
{
super.onRemoved();
removeChild(_background);
removeChild(_image);
removeChild(_name);
removeChild(_brewery);
removeChild(_beerType);
removeChild(_ratingText);
removeChild(_avgRating);
}
}
}
相关文章
- 在 PlayBook 列表中设置自定义标签
- PlayBook 组件的皮肤定制
- 使用容器类布局 PlayBook 应用程序
- 开始使用 BlackBerry PlayBook 和 Adobe AIR
- AIR for Android 的摄像头 API 和地理位置 Exif 数据
历史
- 2011 年 4 月 18 日:初始版本