第四章 构建高效率表现层逻辑
上一章我们结合示例集中地讨论了FLEX开发中及其重要的一系列知识点。那么从本章开始,我们将就每一个主题展开更详细的讨论。本章如题,将集中讨论FLEX在构建高效率的用户界面以便最大可能地丰富用户体验的技巧和总结。
4.1 可视化组件
我们已经直到FLEX标准可视化组件库可以基本上满足所有表现层设计的需要。但是FLEX官方文档并没有详细地告诉我们怎样用这些组件才是高效率的。本节将就作者的一些经验罗列一些总结和教训。本节讨论的话题将在以下几个方面展开。
可视化组件类架构
UIComponent基类
设置组件位置和大小的规则
组件初始化事件
4.1.1 可视化组件类架构
FLEX所有标准可视化组件类都继承自UIComponent基类,有一天当我们自定义一些组件的时候一般也是用UIComponent作为基类,在它的基础上实现各种各样的功能组件。因为UIComponent类里已经封装了很多关于组件可视化的基础属性,动作和行为,如组件高度、宽度、ID属性、基本事件处理、风格、行为、位置等等。参考下图4-1-1-1,这是FLEX可视化组件类架构的一个基本格局。
4.1.2 UIComponent基类
如前所述UIComponent基类封装了很多方便组件可视化的属性方法和行为。但是大部分对于开发者来说都是不必修改的。在利用UIComponent类是我们经常会用到一些非常有用的属性。所有子类都自然继承这些属性。它们是:
属性 | 类型 | 描述 |
Id | String | 当前组件在某一个名字空间内唯一标识 |
scaleX | Number | 显示时横轴相对于原本宽度放大或缩小的倍数 |
scaleY | Number | 显示时纵轴相对于原本高度放大或缩小的倍数 |
X | Number | 组件在其父容器空间横坐标的值 |
Y | Number | 组件在其父容器空间纵坐标的值 |
doubleClickEnabled | Boolean | 一个开关用来决定当前组件是否响应用户鼠标双击事件 |
Enabled | Boolean | 一个开关用来决定当前组件时候响应用户交互的事件 |
Height | Number | 定义当前组件在父容器内的高度 |
Width | Number | 定义当前组件在父容器内的宽度 |
percentHeight | Number | 定义当前组件相对于父容器高度的百分比高度 |
percentWidth | Number | 定义当前组件相对于父容器宽度的百分比高度 |
styleName | String | 用来指定当前组件的样式类 |
toolTip | String | 当鼠标浮在该组件时指定弹出气泡的内容 |
Visible | Boolean | 一个开关用来指定当前组件是否显示。 |
参考示例,
<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="init()">
<mx:UIComponent id="test1" width="100" height="100" scaleX="2" scaleY="2" alpha=".5" x="0" y="10">
</mx:UIComponent>
<mx:UIComponent id="test2" percentWidth="50" percentHeight="40" scaleX="2" scaleY="2" alpha=".5" x="0" y="10">
</mx:UIComponent>
<mx:UIComponent id="test3" width="50%" height="50%" scaleX="2" scaleY="2" alpha=".5" x="0" y="10">
</mx:UIComponent>
</mx:Application>
4.1.3设置组件位置和大小的规则
在布局组件的时候,我们需要定义组件的大小,FLEX SDK支持两种类型的大小定义规则,显式定义和隐式定义。显式定义又分为绝对值定义和百分比定义。总之,对于显式定义我们必须预设当前组件的width/height, percentWidth/percentHeight属性,当然也可以不设置这些属性,而采用组件默认的大小,这就是所谓的隐式定义。参考示例,
<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Panel height="194" width="217" >
<mx:Label width="100%" color="blue"
text="HELLO WIDTH AND HEIGHT"/>
<mx:VBox borderStyle="solid">
<mx:Button label="B1"/>
<mx:Button label="B2"/>
<mx:Button label="B3"/>
<mx:ComboBox/>
</mx:VBox>
</mx:Panel>
</mx:Application>
运行结果:
上例中Panel组件采用显式定义方式,而Label组件的高度,和Button组件采用隐式定义的方式。
对于组件相对于父容器的位置我们通常有两种方式来实现,如果父容器的布局设定为坐标式布局方式(也就是layout属性设置为absolute,或者Canvas组件),我们可以采用设置当前组件的x/y坐标指定组件相对于父容器的位置。另外FLEX也支持一种叫做限制方式的布局标准,在父容器中对能够放置子组件的空间位置做一些限制,如父容器边界上下左右的空白长度,水平中心horizontalCenter或者垂直中心verticalCenter的值的设定等布局当前组件。
参考示例,
<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Panel height="194" width="217"
paddingTop="10" paddingLeft="10" paddingRight="10" paddingBottom="10">
<mx:Label width="100%" color="blue"
text="HELLO WIDTH AND HEIGHT"/>
<mx:VBox borderStyle="solid">
<mx:Button label="B1"/>
<mx:Button label="B2"/>
<mx:Button label="B3"/>
<mx:ComboBox/>
</mx:VBox>
</mx:Panel>
</mx:Application>
运行结果:
在上例中我们设置了Panel组件上下左右空白值。
4.1.4组件初始化事件
组件实例创建的过程基本上可先后分为四个阶段,预初始化、初始化、初始化完毕,更新完毕。与此四个过程紧密相关的事件也响应的有preinitialize, initialize,creationComplete,updateComplete在开发中我们会经常用到这四个事件,比如在这些事件的事件侦听函数中存取远程数据等等。参考一个例子。
<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" initialize="doInit()">
<mx:Script>
<![CDATA[
import mx.controls.Alert;
public function doInit():void{
te.addEventListener("preinitialize",handler);
te.addEventListener("initialize",handler);
te.addEventListener("creationComplete",handler);
te.addEventListener("updateComplete",handler);
}
public function handler(event:Event):void{
te.text+=event.toString();
}
]]>
</mx:Script>
<mx:Panel height="266" width="437" >
<mx:Label width="100%" color="blue" text="TEST COMPONENTS INIT EVENTS:"/>
<mx:VBox borderStyle="solid">
<mx:TextArea id="te" width="407" height="189" />
</mx:VBox>
</mx:Panel>
</mx:Application>
运行结果:
4.2 组件的dataProvider属性
从某种意义上说,FLEX可视化控件是数据的展示载体,在很多标准控件尤其是负责控件都有一个dataProvider属性,这个属性是控件的数据集接口,它的值一般是一个数据集合,可能是Array,Collection,XML,XMLList,ArrayCollection,XMLListCollection等。不同数据展示控件对数据集合的格式要求也是不一样的。
怎样应用dataProvider,大体上有两种方式,一种是内嵌的方式,另一种是外置绑定的方式。两种方式是等价的。
对于第一种方式,可参考示例。
<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" >
<mx:Panel height="266" width="437" >
<mx:ComboBox>
<mx:dataProvider>
<mx:Array>
<mx:Object label="q" data="11"/>
<mx:Object label="w" data="22"/>
<mx:Object label="e" data="33"/>
</mx:Array>
</mx:dataProvider>
</mx:ComboBox>
</mx:Panel>
</mx:Application>
对于第二种方式,
<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" >
<mx:Script>
<![CDATA[
[Bindable]
private var datas:Array=
[{label:"q", data:"11"},
{label:"w", data:"22"},
{label:"e", data:"33"}];
]]>
</mx:Script>
<mx:Panel height="70" width="97" >
<mx:ComboBox dataProvider="{datas}">
</mx:ComboBox>
</mx:Panel>
</mx:Application>
运行结果是一样的,看起来都是:
FELX组件的dataProvider数据类型的种类从结构上可分为两种,一种是线性的平级数组元素结构,另一种是树元素结构。如List,DataGrid,,ComboBox,TileList等属于第一种结构,而Tree,MenuBar,等属于第二种结构。第二种结构一般可以用基于XML的格式来表现,每个节点都是dataProvider数据集合中的一个元素。参考一个树结构的示例,
<?xml version="1.0"?>
<!-- dpcontrols/TreeEvents.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
import flash.events.*;
import mx.events.*;
import mx.controls.*;
private function changeEvt(event:Event):void {
var theData:String = ""
if (event.target.selectedItem.@data) {
theData = event.target.selectedItem.@data;
}
f1.text = event.target.selectedItem.@label +
theData;
}
private function openEvt(event:TreeEvent):void {
f2.text = event.item.@label;
}
]]>
</mx:Script>
<mx:Tree id="tree1" width="150" height="170"
labelField="@label" itemOpen="openEvt(event);"
change="changeEvt(event);">
<mx:XMLListCollection id="datas">
<mx:XMLList>
<node label="q" data="100">
<node label="w" data="70"/>
<node label="e" data="10">
<node label="r" data="2"/>
<node label="t" data="3"/>
<node label="y" data="0" />
<node label="u" data="5" />
</node>
<node label="d" data="15"/>
<node label="v" data="5"/>
</node>
</mx:XMLList>
</mx:XMLListCollection>
</mx:Tree>
<mx:TextArea id="f1" width="160" />
<mx:TextArea id="f2" width="160" />
</mx:Application>
4.3 数据容器类和迭代器类
针对组件的dataProvider属性的操作虽然Array和Collection两种数据集合类型都满足dataProvider的数据格式需求,但是相对于弱类型集合Array,我们更倾向于用Collection集合。主要原因是出于组件数据同步更新的问题,比如如果我们用Array格式的数据集给dataProvider赋值时,当这个Array里的数据变化时,组件数据并不能实时的更新,相反如果我们用Collection集合作为dataProvider的赋值对象,当集合内的数据发生变化时,当前组件可以实时地感受到dataProvider中数据的变化。参考以下示例。
<?xml version="1.0"?>
<!-- charts/ArrayCollectionOfVerboseMXMLObjects.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
public function changeData2():void{
collectionDatas.addItem(newObj);
}
public function changeData1():void{
arrayDatas.push(newObj);
}
]]>
</mx:Script>
<mx:Object id="newObj">
<mx:Month>7</mx:Month>
<mx:Profit>1500</mx:Profit>
<mx:Expenses>500</mx:Expenses>
<mx:Amount>300</mx:Amount>
</mx:Object>
<mx:ArrayCollection id="collectionDatas">
<mx:Object>
<mx:Month>3</mx:Month>
<mx:Profit>1500</mx:Profit>
<mx:Expenses>500</mx:Expenses>
<mx:Amount>300</mx:Amount>
</mx:Object>
<mx:Object>
<mx:Month>4</mx:Month>
<mx:Profit>500</mx:Profit>
<mx:Expenses>300</mx:Expenses>
<mx:Amount>300</mx:Amount>
</mx:Object>
<mx:Object>
<mx:Month>5</mx:Month>
<mx:Profit>1000</mx:Profit>
<mx:Expenses>450</mx:Expenses>
<mx:Amount>250</mx:Amount>
</mx:Object>
<mx:Object>
<mx:Month>6</mx:Month>
<mx:Profit>2000</mx:Profit>
<mx:Expenses>500</mx:Expenses>
<mx:Amount>700</mx:Amount>
</mx:Object>
</mx:ArrayCollection>
<mx:Array id="arrayDatas">
<mx:Object>
<mx:Month>3</mx:Month>
<mx:Profit>1500</mx:Profit>
<mx:Expenses>500</mx:Expenses>
<mx:Amount>300</mx:Amount>
</mx:Object>
<mx:Object>
<mx:Month>4</mx:Month>
<mx:Profit>500</mx:Profit>
<mx:Expenses>300</mx:Expenses>
<mx:Amount>300</mx:Amount>
</mx:Object>
<mx:Object>
<mx:Month>5</mx:Month>
<mx:Profit>1000</mx:Profit>
<mx:Expenses>450</mx:Expenses>
<mx:Amount>250</mx:Amount>
</mx:Object>
<mx:Object>
<mx:Month>6</mx:Month>
<mx:Profit>2000</mx:Profit>
<mx:Expenses>500</mx:Expenses>
<mx:Amount>700</mx:Amount>
</mx:Object>
</mx:Array>
<mx:Panel title="Column Chart" layout="horizontal">
<mx:ColumnChart id="chart1" dataProvider="{collectionDatas}">
<mx:horizontalAxis>
<mx:CategoryAxis
dataProvider="{collectionDatas}"
categoryField="Month"/>
</mx:horizontalAxis>
<mx:series>
<mx:ColumnSeries
xField="Month"
yField="Profit"
displayName="Profit"/>
<mx:ColumnSeries
xField="Month"
yField="Expenses"
displayName="Expenses"/>
</mx:series>
</mx:ColumnChart>
<mx:ColumnChart id="chart2" dataProvider="{arrayDatas}">
<mx:horizontalAxis>
<mx:CategoryAxis
dataProvider="{arrayDatas}"
categoryField="Month"/>
</mx:horizontalAxis>
<mx:series>
<mx:ColumnSeries
xField="Month"
yField="Profit"
displayName="Profit"/>
<mx:ColumnSeries
xField="Month"
yField="Expenses"
displayName="Expenses"/>
</mx:series>
</mx:ColumnChart>
</mx:Panel>
<mx:Button id="bt1" click="changeData1()" label="Change Array Data"/>
<mx:Button id="bt2" click="changeData2()" label="Change Collection Data"/>
</mx:Application>
当点击相关按钮发别触发一个动作,往Array或者ArrayCollection类型的对象中增加一个新对象newObj,根据想象页面的两个Chart都应该更新,但是结果只有dataProvider属性为ArrayCollection的Chart数据被更新,而Array类型的Chart没有被更新。如果想要更新除非Chart组件对象被重画reDraw() 或者其dataProvider被重新赋值一遍。运行结果
Collection集合类是FLEX在数据同步操作方面一个很好的实现,它不仅内置了数据改变事件用来通知绑定这个Collection的组件更新数据显示,而其也封装了很多有用的对于数据Collection操作的函数,如元素的添加、删除、更新、排序操作等。
4.3.1 Collection类
Collection数据容器提供了比弱类型Array更丰富的功能和基于数据的操作管理。从数据的增加、删除、修改、查找、排序、过滤,到数据同步更新等。Collection类实现了IcollectionView接口,这个接口定义了数据排序和数据过滤功能。编程中经常用到的Collection容器类主要有ArrayCollection和XMLListCollection两种。XMLListCollection主要用于树形数据结构的管理,而ArrayCollection主要用于线性数据结构的管理。我们可以在MXML标签中定义一个Collection对象,也可以在ACTIONSCRIPT中定义,如:
<mx:Script>
<![CDATA[
[Bindable]
import mx.collections.ArrayCollection;
public var myAC:ArrayCollection = new ArrayCollection([
"q", "w", "e", "r"]);
]]>
</mx:Script>
和
<mx:ArrayCollection id="collectionDatas">
<mx:Object>
<mx:Month>5</mx:Month>
<mx:Profit>1000</mx:Profit>
<mx:Expenses>450</mx:Expenses>
<mx:Amount>250</mx:Amount>
</mx:Object>
<mx:Object>
<mx:Month>6</mx:Month>
<mx:Profit>2000</mx:Profit>
<mx:Expenses>500</mx:Expenses>
<mx:Amount>700</mx:Amount>
</mx:Object>
</mx:ArrayCollection>
Collection类说到底其实是对Array数组的管理,并且在Collection类定义中有一个source属性,这个属性指定了当前Collection集合类正在管理的Array数组。如
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
public var array:Array = ["x", "c", "v", "b"]);
public var newCollection:ArrayCollection = new ArrayCollection();
newCollection.source = array;
]]>
</mx:Script>
并且我们也可以在MXML标签中将一个外部XML文件中的Array数组转换为一个ArrayCollection实例,如,
<mx:Model source="Data.xml" id="xmlData" />
<mx:ArrayCollection source="{mx.utils.ArrayUtil.toArray(xmlData.loan)}" id="collectionData" />
下面参考一个示例了解关于Collection是怎么进行增删改查和排序操作的
<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="600" initialize="sort()">
<mx:Script>
<![CDATA[
import mx.collections.*;
public function sort():void {
var sort:Sort = new Sort();
sort.fields=[new SortField("type")];
collection.sort=sort;
collection.refresh();
}
public function addItem():void {
collection.addItem({type:"z", data:"zz"});
}
public function removeItem():void{
collection.removeItemAt(collection.length-1);
}
public function updateItem():void{
collection.getItemAt(0).type = 'changed';
}
]]>
</mx:Script>
<mx:ArrayCollection id="collection">
<mx:Array id="array">
<mx:Object type="q" data="qq"/>
<mx:Object type="w" data="ww"/>
<mx:Object type="e" data="ee"/>
<mx:Object type="r" data="rr"/>
<mx:Object type="t" data="tt"/>
<mx:Object type="y" data="yy"/>
<mx:Object type="u" data="uu"/>
</mx:Array>
</mx:ArrayCollection>
<mx:HBox width="100%">
<mx:ComboBox id="cba" rowCount="10" dataProvider="{array}" labelField="type"/>
<mx:ComboBox id="cbb" rowCount="10" dataProvider="{collection}" labelField="data"/>
<mx:Button id="b1" label="Add" click="addItem();"/>
<mx:Button id="b2" label="Remove" click="removeItem();"/>
<mx:Button id="b3" label="Update" click="updateItem();"/>
<mx:Button id="b4" label="Sort" click="sort();"/>
</mx:HBox>
</mx:Application>
其中Collection利用mx.collections.Sort对象生成一个排序实例,设置根据Collection数据集合中哪个属性进行排序。
对于Collection数据过滤功能,主要是给Collection类提供一个数据过滤函数,这个函数返回一个布尔类型对象,Collection中的每个数据元素都将被这个过滤函数遍历一遍,最后生成一个过滤后的数据视图DataView. 参考示例,
<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="600" >
<mx:Script>
<![CDATA[
import mx.collections.*;
public function filter():void{
collection.filterFunction = filterFunction;
collection.refresh();
}
private function filterFunction(item:Object):Boolean{
return (item.type>'e' && item.type < 'x');
}
]]>
</mx:Script>
<mx:ArrayCollection id="collection">
<mx:Array id="array">
<mx:Object type="q" data="qq"/>
<mx:Object type="w" data="ww"/>
<mx:Object type="e" data="ee"/>
<mx:Object type="r" data="rr"/>
<mx:Object type="t" data="tt"/>
<mx:Object type="y" data="yy"/>
<mx:Object type="u" data="uu"/>
</mx:Array>
</mx:ArrayCollection>
<mx:HBox width="100%">
<mx:List id="cba" rowCount="10" dataProvider="{array}" labelField="type"/>
<mx:List id="cbb" rowCount="10" dataProvider="{collection}" labelField="data"/>
<mx:Button id="b1" label="Filter" click="filter();"/>
</mx:HBox>
</mx:Application>
首先指定Collection对象的数据过滤函数为filterFunction,这个函数返回数据元素type属性的指介于字符e和x之间的所有元素,然后更新Collection数据集合。
4.3.2 数据迭代器类
IviewCursor接口定义了在Collection数据视图上遍历的方法,而IcollectionView接口定义了一个createCursor()方法,该方法返回一个IviewCursor对象。编程时我们可以把IviewCursor当作浮在Collection对象数据视图上的一个游标,我们用它来遍历查找每一个Collection数据视图元素。这个游标就是所谓的数据迭代器。
<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
initialize="initData();">
<mx:Script>
<![CDATA[
import mx.collections.*;
public var array:Array = [{label:"qq", data:"qqq"},
{label:"ee", data:"eee"}, {label:"ww", data:"www"}];
[Bindable]
public var collection:ArrayCollection;
public var cursor:IViewCursor;
public function initData():void {
collection = new ArrayCollection(array);
}
public function testCollection():void {
cursor=collection.createCursor();
string.text=" NOW CURSOR IS AT: " + cursor.current.label;
var removedItem:String=String(cursor.remove());
string.text+="/nNOW CURSOR IS AT: " + cursor.current.label;
cursor.insert({label:"zz", data:"zzz"});
string.text+="/nNOW CURSOR IS AT:: "
+ cursor.current.label;
}
]]>
</mx:Script>
<mx:ComboBox rowCount="10" dataProvider="{collection}"/>
<mx:TextArea id="string" height="75" width="350"/>
<mx:Button label="TEST" click="testCollection();"/>
</mx:Application>
点击按钮testCollection()动作被触发,首先创建游标对象
cursor=collection.createCursor();
然后分别添加和删除一个数据元素查看当前游标的位置。
4.3.3 Collection数据事件处理
编程时我们有可能会在当Collection数据结构改变时相应地做一些动作,所以首先我们必须捕捉和辨认当前数据集合的事件种类。Collection数据事件被封装在CollectionEvent类中,事件的种类或者类型则被封装在CollectionEventKind类中,这些种类主要有CollectionEventKind.ADD, CollectionEventKind.REPLACE, CollectionEventKind.REMOVE, CollectionEventKind.REFRESH, CollectionEventKind.UPDATE, CollectionEventKind.RESET等。参考示例展示了怎样捕捉和处理Collection数据事件。
<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="984" height="600" layout="horizontal" >
<mx:Script>
<![CDATA[
import mx.events.*;
import mx.collections.*;
import mx.controls.Alert;
public function collectionEventHandler(event:CollectionEvent):void {
switch(event.kind) {
case CollectionEventKind.ADD:
Alert.show("Location "+ event.location + " added");
break;
case CollectionEventKind.REMOVE:
Alert.show("Location "+ event.location + " removed");
break;
case CollectionEventKind.REPLACE:
Alert.show("Location "+ event.location + " Replaced");
break;
case CollectionEventKind.UPDATE:
Alert.show("Location updated");
break;
}
}
public function add():void {
collection.addItem({first:firstInput.text, last:lastInput.text
});
clear();
}
public function remove():void {
// Make sure an item is selected.
if (dg.selectedIndex >= 0) {
collection.removeItemAt(dg.selectedIndex);
}
}
public function update():void {
// Make sure an item is selected.
if (dg.selectedItem !== null) {
collection.setItemAt({first:firstInput.text, last:lastInput.text}, dg.selectedIndex);
}
}
public function changeHandler():void {
clear();
firstInput.text = dg.selectedItem.first;
lastInput.text = dg.selectedItem.last;
}
public function clear():void {
firstInput.text = "";
lastInput.text = "";
}
public function labelFunc(item:Object):String {
return item.first + " " + item.last;
}
]]>
</mx:Script>
<mx:ArrayCollection id="collection"
collectionChange="collectionEventHandler(event)">
<mx:source>
<mx:Object first="AA" last="BB" />
<mx:Object first="SS" last="DD" />
<mx:Object first="FF" last="GG" />
<mx:Object first="CC" last="VV" />
<mx:Object first="NN" last="MM" />
</mx:source>
</mx:ArrayCollection>
<mx:Panel title="COLLECTION EVENT CATCH DEMO" layout="vertical" width="486" height="339">
<mx:DataGrid width="450" id="dg" dataProvider="{collection}"
change="changeHandler()" x="102" y="0">
<mx:columns>
<mx:DataGridColumn dataField="first" headerText="First"/>
<mx:DataGridColumn dataField="last" headerText="Last"/>
</mx:columns>
</mx:DataGrid>
<mx:Form x="108" y="148">
<mx:FormItem label="First">
<mx:TextInput id="firstInput"/>
</mx:FormItem>
<mx:FormItem label="Last">
<mx:TextInput id="lastInput"/>
</mx:FormItem>
</mx:Form>
<mx:HBox x="108" y="266">
<mx:Button label="Add" click="add()"/>
<mx:Button label="Update" click="update()"/>
<mx:Button label="Remove" click="remove()"/>
<mx:Button label="Clear" click="clear()"/>
</mx:HBox>
</mx:Panel>
</mx:Application>
4.4 深入理解ItemRenderer
FLEX提供了很多标准可视化控件,但也不是说它们完全能够满足我们富表现层的所有需求。一般情况下FLEX控件都有默认的数据展示方式,比如DataGrid控件它对数据的默认展示是字符串分组列表的方式,但如果需要在某一列根据dataProvider提供的数据展示一幅图片又或更复杂的的元素集合该怎么办呢?这时我们通常要用到itemRenderer属性。itemRenderer提供了开发者按照自己的意愿展示数据的弹性。关于怎么定义itemRenderer,基本上有两种方式,一种是内置定义方式,另一种则是组件自定义方式。说到底itemRenderer是基于当前组件对数据的另一种展示方式,那么这个数据是什么呢?答案就是data变量,每个自定义的Renderer类不管是内置定义方式还是组件自定义方式,开发者都可以直接应用一个名字叫data的Object类型变量,这个变量当我们的Renderer类初始化时被应用自动初始化,任何时刻它都包含数据集中某一个元素的所有信息,可以在形式上理解为data = 组件id.dataProvider.getItemAt(i).
4.4.1 内置定义方式
所谓内置定义方式,就是开发者不必另外定义一个Renderer类,而是采用内置的方式直接在组件内部现场定义Renderer的外观和样式。参考示例,
<?xml version="1.0"?>
<!-- charts/CustomPlotRendererAS.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="912" height="596">
<mx:Script><![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
public var collection:ArrayCollection = new ArrayCollection([
{Month:"January", Profit:2000, selected:true},
{Month:"February", Profit:1000, selected:true},
{Month:"March", Profit:1500, selected:false},
{Month:"April", Profit:500, selected:true},
{Month:"May", Profit:1000, selected:false},
{Month:"June", Profit:2000, selected:true}
]);
]]></mx:Script>
<mx:Panel title="ItemRenderer Demo">
<mx:DataGrid id="grid" width="100%" height="100%" dataProvider="{collection}">
<mx:columns>
<mx:DataGridColumn dataField="Month" headerText="Month"/>
<mx:DataGridColumn dataField="Profit" headerText="Profit"/>
<mx:DataGridColumn dataField="selected" headerText="Select">
<mx:itemRenderer>
<mx:Component>
<mx:CheckBox selected="{data.selected}"/>
</mx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
</mx:columns>
</mx:DataGrid>
</mx:Panel>
</mx:Application>
注意这段代码
<mx:DataGridColumn dataField="selected" headerText="Select">
<mx:itemRenderer>
<mx:Component>
<mx:CheckBox selected="{data.selected}"/>
</mx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
这里以内置定义的方法直接在dataField绑定在selected属性的列中用CheckBox展示selected的值而不是true或false的字符串。
4.4.2 组件自定义方式
如果Renderer的结构有些复杂,那么就最好使用组件自定义方式展示数据了。这种方式比内置定义方式最大的好处是可以在整个应用的范围内重用这个我们自定义的Renderer组件。而内置定义方式是不能做到这一点的。采用这种方式,第一步就是以自定义组件的方式定义Renderer类。如checkBoxRenderer.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:CheckBox xmlns:mx="http://www.adobe.com/2006/mxml" selected="{data.selected}" >
</mx:CheckBox>
第二步就是利用组件的itemRenderer的属性设置Renderer的目标对象,如
<?xml version="1.0"?>
<!-- charts/CustomPlotRendererAS.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="912" height="596">
<mx:Script><![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
public var collection:ArrayCollection = new ArrayCollection([
{Month:"January", Profit:2000, selected:true},
{Month:"February", Profit:1000, selected:true},
{Month:"March", Profit:1500, selected:false},
{Month:"April", Profit:500, selected:true},
{Month:"May", Profit:1000, selected:false},
{Month:"June", Profit:2000, selected:true}
]);
]]></mx:Script>
<mx:Panel title="ItemRenderer Demo">
<mx:DataGrid id="grid" width="100%" height="100%" dataProvider="{collection}">
<mx:columns>
<mx:DataGridColumn dataField="Month" headerText="Month"/>
<mx:DataGridColumn dataField="Profit" headerText="Profit"/>
<mx:DataGridColumn dataField="selected" headerText="Select" itemRenderer="CustomBox"/>
</mx:columns>
</mx:DataGrid>
</mx:Panel>
</mx:Application>
4.4.3复杂Renderer处理
真实的应用可能会碰到复杂的数据展示,对于这类复杂的Renderer类,第一我们要注意data数据的处理;第二注意名字空间的使用。比如这里有个仪表盘的Renderer自定义类型Scaler.mxml
主程序ScalerRender.mxml,
<?xml version="1.0"?>
<!-- charts/CustomPlotRendererAS.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="1088" height="596">
<mx:Model source="/scaler/data.xml" id="data" />
<mx:ArrayCollection source="{mx.utils.ArrayUtil.toArray(data.company)}" id="array" />
<mx:Panel title="ItemRenderer Demo" width="1046" height="493">
<mx:DataGrid id="grid" width="100%" height="100%" dataProvider="{array}">
<mx:columns>
<mx:DataGridColumn dataField="companyName" headerText="companyName"/>
<mx:DataGridColumn dataField="scalerItem" headerText="scalerItem"/>
<mx:DataGridColumn dataField="currentValue" headerText="currentValue"/>
<mx:DataGridColumn itemRenderer="FullScaler" headerText="scaler"/>
</mx:columns>
</mx:DataGrid>
</mx:Panel>
</mx:Application>
仪表盘Render类文件FullScaler.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" creationPolicy="all" verticalScrollPolicy="off"
horizontalScrollPolicy="off" width="101" height="119"
xmlns:scaler="scaler.*" creationComplete="doInit()">
<mx:Fade id="fadeOut" duration="1000" alphaFrom="1.0" alphaTo="0.0"/>
<mx:Fade id="fadeIn" duration="1000" alphaFrom="0.0" alphaTo="1.0"/>
<mx:Script>
<![CDATA[
import mx.core.Application;
import mx.utils.ArrayUtil;
import mx.controls.Alert;
import mx.collections.ArrayCollection;
import mx.graphics.SolidColor;
import CustomizedFontsEngine;
public var startvalue:Number;
[Embed(source="/assets/logo.png")]
[Bindable]
public var iconSymbol:Class;
public var currentvalue:Number;
public var maxvalue:Number;
public var zonevalue1:Number;
public var zonevalue2:Number;
public var duration:Number;
public var scalername:String;
public var colorfills:String;
public var tempFills:Array=new Array();
public var zonedata:ArrayCollection= new ArrayCollection();
public var colaphiFont:String = CustomizedFontsEngine.getFontInstance(CustomizedFontsEngine.CHINESE_HEITI);
public function doInit():void {
currentvalue = data.currentValue;
maxvalue = data.endValue;
zonevalue1 = data.zoneValue1;
zonevalue2 = data.zoneValue2;
startvalue = 0;
duration = 1500;
colorfills = 'RGY'
zonedata.removeAll();
//mx.controls.Alert.show('[zone1]:' + zonevalue1 +'[zone2]:'+ zonevalue2 + ':[start]:' + startvalue + '[current]:' + currentvalue + '[maxvalue]:' + maxvalue);
var zoneObj3:Object = new Object();
zoneObj3.percentage = Math.abs(maxvalue - zonevalue2);
zonedata.addItem(zoneObj3);
var zoneObj2:Object = new Object();
zoneObj2.percentage = Math.abs(zonevalue2-zonevalue1);
zonedata.addItem(zoneObj2);
var zoneObj1:Object = new Object();
zoneObj1.percentage = Math.abs(zonevalue1-startvalue);
zonedata.addItem(zoneObj1);
var zoneObj4:Object = new Object();
zoneObj4.percentage = Math.abs(maxvalue-startvalue)/3;
zonedata.addItem(zoneObj4);
for(var element:Number=0;element<colorfills.length;element++){
var currentStr:String = colorfills.charAt(element);
switch(currentStr){
case 'R' :
var tempColor:SolidColor = new SolidColor(0xff0000,.8);
tempFills.push(tempColor);
break;
case 'Y' :
var tempColor:SolidColor = new SolidColor(0xffff00,.8);
tempFills.push(tempColor);
break;
case 'G' :
var tempColor:SolidColor = new SolidColor(0x66cc00,.8);
tempFills.push(tempColor);
break;
}
}
tempFills.reverse();
var tempColor:SolidColor = new SolidColor(0x000000,.8);
tempFills.push(tempColor);
//reset the data binding
runner.angleTo = Math.abs(currentvalue-startvalue)*270/Math.abs(maxvalue-startvalue);
runner.angleFrom = 0;
runner.target = zhizhen;
runner.duration = duration;
digitalShow.label = currentvalue+'';
nameShow.text = scalername;
digitalShow.setStyle("disabledColor",getDisabledColor());
if(runner.isPlaying)runner.end();
runner.play();
}
public function getDisabledColor():uint{
var rightColor:uint = new uint(0xFFFFFF);
if(Math.abs(currentvalue-startvalue)<Math.abs(zonevalue1-startvalue) && Math.abs(currentvalue-startvalue)>0){
var tempCo1:SolidColor = SolidColor(tempFills[2]);
rightColor = tempCo1.color;
}else if(Math.abs(currentvalue-startvalue)>Math.abs(zonevalue1-startvalue) && Math.abs(currentvalue-startvalue)<Math.abs(zonevalue2-startvalue)){
var tempCo2:SolidColor = SolidColor(tempFills[1]);
rightColor = tempCo2.color;
}else if(Math.abs(currentvalue-startvalue)>Math.abs(zonevalue2-startvalue) && Math.abs(currentvalue-startvalue)<Math.abs(maxvalue-startvalue)){
var tempCo3:SolidColor = SolidColor(tempFills[0]);
rightColor = tempCo3.color;
}
if(rightColor == new uint("0xff0000")){
//mx.controls.Alert.show("RED ZONE ALERT!","ALERT!",4.0,null,null,iconSymbol,4.0);
}
return rightColor;
}
]]>
</mx:Script>
<mx:SeriesInterpolate id="eff" elementOffset="200" minimumElementDuration="250" duration="2000"/>
<scaler:FullRotation id="runner" angleFrom="0" angleTo="{Math.abs(currentvalue-startvalue)*270/Math.abs(maxvalue-startvalue)}" duration="{duration}"/>
<mx:Image source="@Embed('/assets/yibiaopan_full.png')"
id="scalerbgimage" x="5" y="1" hideEffect="{fadeOut}" showEffect="{fadeIn}"/>
<mx:Image source="@Embed('/assets/zhizhenyuan.png')"
id="scalerbgimage1" x="41" y="37" hideEffect="{fadeOut}" showEffect="{fadeIn}"/>
<mx:PieChart innerRadius=".70" id="scalerchart" height="114" width="123" dataProvider="{zonedata}" alpha="0.6" x="-11" y="-10" >
<mx:series>
<mx:PieSeries showDataEffect="{eff}" startAngle="-45" fills="{tempFills}" id="pieseries" field="percentage">
</mx:PieSeries>
</mx:series>
</mx:PieChart>
<mx:Image source="@Embed('/assets/zhizhen.gif')" id="zhizhen" width="43" x="52" y="47" height="20"/>
<mx:Box width="101" verticalAlign="bottom" horizontalScrollPolicy="off" verticalScrollPolicy="off" height="25" horizontalGap="0" verticalGap="0" y="93">
<mx:Label id="nameShow" text="{scalername}" fontFamily="{colaphiFont}" fontSize="11" width="100%" height="100%" textAlign="center"/>
</mx:Box>
<mx:Button id="digitalShow" label="{currentvalue}" x="20" y="58" width="64" borderColor="#000000" cornerRadius="7" focusThickness="0" fontThickness="10" horizontalGap="0" textAlign="center" textRollOverColor="#FFFFFF" fontGridFitType="pixel" enabled="false" emphasized="true" disabledColor="{getDisabledColor()}" fillColors="0xaaaaaa" height="17"/>
</mx:Canvas>
其中需要的两个旋转效果辅助类FullRotationInstance.as和FullRotation.as
FullRotationInstance.as代码为,
package scaler
{
// myEffects/RotationInstance.as
import mx.effects.effectClasses.TweenEffectInstance;
import mx.effects.Tween;
import mx.controls.Alert;
public class FullRotationInstance extends TweenEffectInstance
{
// Define parameters for the effect.
public var angleFrom:Number;
public var angleTo:Number;
public function FullRotationInstance(targetObj:*) {
super(targetObj);
}
// Override play() method class.
override public function play():void {
// All classes must call super.play().
super.play();
// Create a Tween object. The tween begins playing immediately.
var tween:Tween =
createTween(this, angleFrom+135, angleTo+135, duration);
}
// Override onTweenUpdate() method.
override public function onTweenUpdate(val:Object):void {
target.rotation = val;
}
// Override onTweenEnd() method.
override public function onTweenEnd(val:Object):void {
// All classes that implement onTweenEnd()
// must call super.onTweenEnd().
super.onTweenEnd(val);
}
}
}
FullRotation.as文件代码,
package scaler{
import mx.effects.TweenEffect;
import mx.effects.EffectInstance;
//import mx.effects.IEffectInstance;
public class FullRotation extends TweenEffect
{
// Define parameters for the effect.
public var angleFrom:Number = 0;
public var angleTo:Number = 360;
// Define constructor with optional argument.
public function FullRotation(targetObj:* = null) {
super(targetObj);
instanceClass= FullRotationInstance;
}
// Override getAffectedProperties() method to return "rotation".
override public function getAffectedProperties():Array {
return ["rotation"];
}
// Override initInstance() method.
override protected function initInstance(inst:mx.effects.IEffectInstance):void {
super.initInstance(inst);
FullRotationInstance(inst).angleFrom = angleFrom;
FullRotationInstance(inst).angleTo = angleTo;
}
}
}
外部数据文件data.xml
<?xml version="1.0" encoding="UTF-8"?>
<homeScalerData>
<company>
<companyName>MICROSOFT</companyName>
<scalerItem>INCOME</scalerItem>
<scalerItemId>2</scalerItemId>
<currentValue>36.4</currentValue>
<startValue>0.0</startValue>
<zoneValue1>52.0</zoneValue1>
<zoneValue2>77.0</zoneValue2>
<endValue>100.0</endValue>
</company>
<company>
<companyName>SONY</companyName>
<scalerItem>SALES</scalerItem>
<scalerItemId>1</scalerItemId>
<currentValue>181.92</currentValue>
<startValue>0</startValue>
<zoneValue1>1000</zoneValue1>
<zoneValue2>1300</zoneValue2>
<endValue>1500</endValue>
</company>
<company>
<companyName>CITIGROUP</companyName>
<scalerItem>EXCHANGE RATE</scalerItem>
<scalerItemId>2</scalerItemId>
<currentValue>91.1</currentValue>
<startValue>0.0</startValue>
<zoneValue1>62.0</zoneValue1>
<zoneValue2>97.0</zoneValue2>
<endValue>100.0</endValue>
</company>
</homeScalerData>
运行结果:
4.5 深入理解ItemEditor
itemEditor属性允许用户直接改变数据集控件单元的值,而不需要通过弹出窗口的方式改变当前选中单元的值。FLEX3标准实现中List,DataGrid,Tree等组件支持itemEditor功能。当用户开始编辑单元格时所发生的过程是这样的。
一.通过用户鼠标或者键盘,单元格得到编辑焦点Focus.
二.itemEditBegining事件被触发,开发者可以利用这个事件有选择地控制哪些单元格可以被编辑,而哪些不可以。
三.用户开始编辑单元格。
四.用户完成编辑过程,当被编辑单元格失去焦点时,编辑过程结束。
五.itemEditEnd事件被触发,单元格编辑器关闭,并更新单元格的值,开发者可以捕捉这个事件,做一些有用的操作比如验证或者再更新这个单元格的新值是否符合要求。
六.新值显示在单元格中。
上面提到的对于List,DataGrid,Tree控件都有一个editable属性,这个属性默认为false意指当前组件所有单元格都是不可编辑的,相反设为true时,所有单元格都编程可以编辑的了。如果你只希望DataGrid的某一列是可编辑的那么可以设置DatagridColumn的editable属性为true或false
。和itemRenderer属性类似,itemEditor默认为字符串编辑的itemEditor,也可以用组件自定义的方式来设置itemEditor。注意如果你想用本单元格的自定义Renderer组件作为itemEditor,要设置当前组件的rendererIsEditor属性为true,这时你再设置itemEditor属性,也不会起作用的。对于更新后的数据返回问题,如果你这个数据是基本类型String,则数据会自动更新,但是如果是个复杂类型如Date(你用DateField作为itemEditor)类型,则必须显式的指出要更新的日期对象属性为哪一个,语法是editDataField=selectedDate,参考示例,
<?xml version="1.0"?>
<!-- itemRenderers/tree/MainTreeEditor.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="700" height="700">
<mx:Script>
<![CDATA[
import mx.events.ListEvent;
private var cont:Object =
{label: "top", children: [
{label: "QQ", flag: true, ids: "111111", children: [
{label: "WW", flag: true, ids: "222222"},
{label: "EE", flag: false, ids: "333333"}
]},
{label: "RR", flag: true, ids: "444444", children: [
{label: "YY", flag: false, ids: "666666"},
]},
{label: "UU", flag: true, ids: "777777"}
]};
private function init(obj:Object):void {
tree.dataProvider = obj;
}
private function disable(event:ListEvent):void {
if(event.rowIndex==0) {
event.preventDefault();
}
}
public function process(event:ListEvent):void {
event.preventDefault();
tree.editedItemRenderer.data.id = TreeEditor(event.currentTarget.itemEditorInstance).ids.text;
tree.destroyItemEditor();
tree.dataProvider.notifyItemUpdate(tree.editedItemRenderer);
}
]]>
</mx:Script>
<mx:Tree id="tree"
width="400" height="400"
editable="true"
itemEditor="TreeEditor"
editorHeightOffset="75" editorWidthOffset="-90"
editorXOffset="40" editorYOffset="40"
creationComplete="init(cont);"
itemEditBeginning="disable(event);"
itemEditEnd="process(event);"/>
</mx:Application>
相应的itemEditor类TreeEditor.mxml
<?xml version="1.0" encoding="iso-8859-1"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:HBox>
<mx:Text text="ID:"/>
<mx:TextInput id="ids"
width="150"
text="{data.ids}"
change="id=ids.text;"/>
</mx:HBox>
</mx:VBox>
4.6 总结
本章着重讲述了构建高效率表现层逻辑的个很中要的主题可视化组件和数据容器类,有了这些和前三章的知识,相信读者已经可以根据自己的需要设计和实现不错的表现层了。下一章,我们将重点讨论FLEX各式各样的图标组件。有了它们,表现层的核心主题我们已经囊括地初步多了。请继续读下去。