数据驱动组件是通过绑定机制监视数据源,通过渲染机制呈现数据的一类组件,数据驱动组件包括基本列表、高级列表、菜单和导航按钮组件,其共同特点是通过dataProvider:Object属性,采取绑定方式工作,Repeater虽然不能算作组件,但仍然采用数据驱动的思路创建重复组件,可以看出这种机制是应用的非常成功的。
dataProvider属性
对于Flex组件和容器来说,dateProvider属性与绑定有着特殊的渊源,dataProvider不仅是数据的来源而且还要进行渲染,是数据和显示分离的重要标志。列表要求数据源不仅能被监测还要便于呈现,为此dataProvier具有将低级对象转化为集合的能力,这些转化过程都在dataProvider的set方法中实现,而用于普通绑定的属性是不会做这些工作的。
自动转化规则
dataProvider的转化规则如下:
如果类型为 ICollectionView,则不执行任何转换。
如果类型为 IList,则转换为 ListCollectionView。
如果类型为 Array,则转换为 ArrayCollection。
如果类型为 XML 或 XMLList,则转换为 XMLListCollection。
在其他情况下,转换为单一元素 ArrayCollection。
转化规则是依据如下继承关系而来的:
上图要说明ICollectionView是可视集合所需的接口,ICollectionView接口定义过滤、排序、选取等视觉呈现所需能力,IList为集合添加数据操作接口,ListCollectionView是这两个接口的完整实现,是拓展集合的重要基石。
选择合适的集合类型
dataProvider最常用的集合类型除了扩展ListCollectionView的ArrayCollection和XMLListCollection还有Model,由于使用频繁,Flex提供如果了mxml定义方式,mxml语法大大简化了静态初始化集合的代码,当指定数据驱动组件创数据源时,应直接定义成这3种类型,避免直接操作低级对象带来的不明确性。
ArrayCollection
ArrayCollection非常适合线性数据,能够使用[]和for循环来保留使用Array的习惯,相比XMLListCollection和Model,ArrayCollection的元素没有类型限制。ArrayCollection描述树形结构有诸多的缺点,原因如下:
首先多维数组难以阅读和修改,ArrayCollection也是如此,再者ArrayCollection是唯一不修改元素的集合,而XMLLListCollection和ObjectProxy通过对元素动态增补来实现绑定,要进行多级绑定,需手动构建嵌套的ArrayCollection数组,没有多级转化Array的能力,也没有读取外部文件的能力,没有更好的理由,不要使用ArrayCollection描述数型结构。
XMLListCollection
XMLListCollection非常适合树型结构数据,因为XML对树形数据的操作具有天生的优势,且可以从外部文件或者服务器直接获取,经过XMLListCollection包装后的XML可以自动监测深层节点的变化,但不能使用XML操作符(使用IList定义的接口)。XMLListCollection的优点是可以直接定义子级,而ArrayCollection和Model需要使用children属性定义子级。
使用XMLListCollection描述线性数据并不具有优势,当dataProvider转化低级对象时,在mxml的绑定语法中,如果表达式中包含的XML需要明确的指出类型,否则会导致转化失败。例如将xml定义为Object将不能识别,运行时会弹出类似” warning: unable to bind to property 'city' on class 'XMLList'”的警告。另外更加严重的一个问题是,当XML中包含相同名称的节点时,Flex可能会向XML中加入标记节点
来进行唯一标识,标记节点虽然在显示时会被过滤掉,但会影响我们再次进行节点搜索,而ArrayCollection是依靠索引来检索元素的,不存在节点名称相同的情况。
Model
Model可描述线性数据和树型数据,Model并不扩展ListViewCollection,因此Model具有体积小的优点,可以直接当作数据储存器使用,更难得的是mxml支持Model元素使用绑定,使得Model成为优秀的数据收集器。在绑定机制上Model能够自动进行深层绑定,还能从外部读取XML文件,拥有XMLListCollection的特点,但由于抛开了ListViewCollection这个基石,操作性上不如ArrayCollection和XMLListCollection全面,作为线性数组时仍需要通过children字段定义子级,相比之下Model更偏向于数据储存和信息收集。
ListBase
ListBase是基础列表组件的基石,考虑到列表组件广泛使用性,ListBase拥有众多的成员和极强的扩展能力。
列表大小与行和列显示
columnCount:int和rowCount:int设置显示的列和行数量,columnWidth:Number和rowHeight:Number设置列宽和行高。行列属性并非适用于所有列表,还受到自动换行的影响。
列宽、列数与列表宽度
columnCount:int
对于只有一列的List和Tree来说columnCount意义并不大,对于DataGrid列宽由DataGridColumn设置,columnCount适用于多列的TileList和HorizontalList的显示列数。 columnCount,columnWidth和列表的width属性并不互相制约,列表组件通过滑动条、剪裁和添加空白来维持各个值。
columnWidth:Number
对于多列的TileList和HorizontalList有两种方式决定列表大小,一种是通过设置columnWidth和columnCount,这种方式让内容显示比较精确。另一种是通过columnWidth和width属性让列表自动计算显示的行数,这种方式适用于对列表宽度有要求的情况,例如列表宽度适用百分比。如果同时设置了列宽、列数和列表宽度,那么columnCount会失效。
行高、行数与列表的高度
固定的行高
rowCount和rowHeight对于所有列表均有效,列表默认采用固定行高,不自动换行时,行高、行数与列表高度的设置方法与列相同。当设置固定行高时,超出显示范围的区域将被剪裁。
变化的行高
当variableRowHeight:Boolean属性为true时列表可以拥有不同的行高,不同的行高适用于两种情况,一是条目显示内容不一样时,例如有些条目包括图标和文字标签,有些条目只包括文字标签,拥有图标条目所需的行高要大与只有文字标签的条目,设置variableRowHeight为true后列表会自动适应每行的行高。另外一个用途就是自动换行,设置variableRowHeight为true和列宽后将wordWrap:Boolean为true可以启用自动换行,自动换行后列表根据文本内容设置行高。变化的行高打破了计算列表高度的方式,rowHeight和rowCount均会无效,只能设置height属性,列表自动确定行高和行数。
默认数值
列宽默认为50,列数默认为4,行高默认为20,行数,显示行数为7,rowHeight默认为20。如果不设置任何与行列相关的属性,列表将采取第一种方式计算其大小。
选择条目
要使列表的条目可以选择,需保证selected:Boolean属性为true,不过默认情况下列表都是可以选择条目的,selectedIndex:int和selectedItem:Object是广泛使用的条目选择属性且可以作为绑定源。设置selectedIndex和设置selectedItem的效果相同,如果条目为数值则selectedItem按数值比较,如果为引用则按引用比较。对于List组件selectedIndex默认为-1,selectedItem默认为null,ComboBox在聚合列表后将自身的selectedIndex默认为0,selectedItem为第一项。allowMultipleSelection:Boolean属性允许是开启多选功能,如果开启多选功能则selectedIndices:Array和selectedItems:Array记录选择的条目索引和选项数组。
ListBase提供一个改善选择条目体验的属性:allowDragSelection:Boolean,这个属性允许先按下鼠标再移动到条目,主要针对下拉列表,如ComboBox组件,如果你的自定义组件也需要做成下拉列表式,因该考虑到这个属性。
拖放条目
列表支持内部条目的拖放,拖放细则由各个列表子类实现,ListBase中定义启动拖放的属性,如下:
dragEnabled:Boolean
是否允许拖动条目
dropEnabled:Boolean
是否允许放置条目
dragMoveEnabled:Boolean
移动还是复制条目
详细内容请阅读拖放章节。
单元渲染器
单元渲染器将数据条目实例化为指定的视觉组件,单元渲染器由列表组件的itemRenderer:IFactory属性指定,Flex的内置列表有默认的单元渲染器,如下:
List
ListItemRenderer
TileList
TileListItemRenderer
HorizontalList
TileListItemRenderer
DataGrid
DataGridItemRenderer
Tree
TreeItemRenderer
默认渲染器满足列表组件的基本需求,你通过将自定义渲染器来满足项目需求,单元渲染器只能有一个,不能单独为条目指定单元渲染器。
单元渲染器的渲染机制
列表组件的数据条目数量可能非常巨大,如果为每个数据条目创建一个单元渲染器实例会立即耗尽所有的内存,实际上并不需要为每一个数据条目创建单元渲染器,因为用户可以看到的数据条目是有限的,只要为可视的部分创建单元渲染器即可,列表组件在用户滚动到其它条目时也并非频繁的销毁和新建实例,而是对已有的单元渲染器循环利用,这一机制类似水车打水,如图:
实际上列表组件准备的单元渲染器要比可视数量多一些,多的单元渲染器用于性能优化,性能优化的方式使用奴隶与死囚优化法则,创建新的渲染器时如果缓存中有则从缓存中去,如果没有创建新的渲染器。单元渲染器在性能上明显高于容器+Repeater组件,但多了换算的步骤,为了顺利查找数据条目和单元渲染器对应的关系,ListBase提供了全面的转化方法:
通过单元渲染器获取数据条目
在获取单元渲染器引用的情况下,可以直接通过data属性获取数据条目,无需调用ListBase的方法。
通过单元渲染器获取条目索引
itemRendererToIndex(itemRenderer:IListItemRenderer):int提供单元渲染器到条目索引的转换,对于Tree组件返回显示索引。
通过条目索引获取单元渲染器
反过来,如果知道数据条目索引,可以通过indexToItemRenderer(index:int):IListItemRenderer进行转换,注意只能查找已被实例化的单元渲染器,即条目索引必须被载入单元渲染器。要判断单元渲染器是否装载某条数据,可以调用isItemVisible(item:Object):Boolean方法。对于Tree组件,index为显示索引。
通过数据条目获取单元渲染器
itemToItemRenderer(item:Object):IListItemRenderer是通过数据条目查找单元渲染器的方法,同indexToItemRenderer()方法一样要求条目已被载入到单元渲染器。
选取单元渲染器
了解了单元渲染器的渲染机制后,我们知道了数据和单元渲染器之间的关系和转化方法,此时通过鼠标选取单元渲染器是件容器的事情。ListBase的selectedIndex和selectedItem属性提供了选取条目索引和条目数据的能力,但不直接提供选取单元渲染器的属性,要查找当前选择条目的单元渲染器,应该在change事件中获取,ListEvent提供单元渲染器的引用。当然你也可以通过indexToItemRenderer()和itemToItemRenderer()方法将索引或数据条目进行转化,找到数据条目后可以通过isItemSelectable(data:Object):Boolean和isItemSelected(data:Object):Boolean方法判断单元渲染器是否可选和是否选中,通过isItemHighlighted(data:Object):Boolean方法判断单元渲染器是否被加亮。
在单元渲染器中编写代码
在单元渲染器中编写代码比起在列表事件中编写代码具有优势,单元渲染器内部能够直接通过data属性获取条目数据,通过listData获取单元信息,避开了转化过程且符合封装的需求。当滚动列表时,单元渲染器会不断的更新数据,频繁触发dataChange事件,如果包含修改视觉外观的代码要同时考虑到普通外观和修改后的外观更新。
单元渲染器的数据传递机制
单元渲染器数据传递过程是列表组件设置单元渲染器特定属性的过程,这些属性通过各类接口制定,相关的接口包括:IListItemRenderer, IDataRenderer, IDropInListItemRenderer,下面我们看看这些接口意义及实现内幕。
通过data属性传递数据条目
所有的列表都是通过设置单元渲染器的data:Object属性传递数据条目的,data属性是IDataRenderer接口定义的,所有单元渲染器都必须实现此接口。data通常定义为get/set属性,在data的set方法可以加入逻辑代码。通常有3中方式分析和设置组件自身属性来更改其视觉呈现:
在data的set方法中添加逻辑代码
在dataChange事件中添加,设置data后会触发dataChange事件
使用绑定
绑定是最简单直接的做法,但是绑定不能加入代码逻辑,在set方法中编写逻辑是标准的方法,但意味着要覆盖基类属性,使用dataChange事件是一种被动的做法,当你不想覆盖data属性时是很好的选择。
通过listData:BaseListData属性传递单元信息
对于单元渲染器,只知道需要呈现的数据条目是不够的,还需要知道条目所处的单元信息,例如知道单元渲染器所在的行和列后才能明确当前数据条目处于数据表格的哪一行哪一列,知道显示的文本和图标后才能创建相关的对象,单元渲染器需要这些信息进行渲染。单元信息记录在单元渲染器的listData:BaseListData属性中,被IDropInListItemRenderer接口所定义,listData是通过单元渲染器获取数据条目和渲染信息的重要方式。
不同的列表组件会将不同的单元信息传递给单元渲染器,对于List,HorizontalList,TileList类型为ListData,对于DataGrid类型为 DataGridListData,对于Tree类型为 TreeListData,对于Menu类型为MenuListData,BaseListData是这些类型的基类,记录的信息包括:
owner:IUIComponent
所属的列表
rowIndex:int
单元渲染器所在的行
columnIndex:int
单元渲染器所在的列
label:String
单元渲染器标签
uid:String
唯一标识
子类会记录一些额外的信息,例如ListData使用labelField:String和iconClass:Class属性记录字段名和图标类,TreeListData记录了节点的层级、打开状态等,如果没有这些信息,就只能通过数据条目进行硬编码,例如在DataGrid中匹配studentName字段的单元渲染器不能用作其它列,因为单元渲染器不知道处于哪一列,知道单元信息后,就可以将多列设置为同一个单元渲染器,根据所在列数来显示,实现IDropInListItemRenderer接口的单元渲染器又称为“嵌入式”单元渲染器。listData总是和data属性同步更新并在data之前设置,但listData不会触发dataChange事件。在具有IDropInListItemRenderer接口的组件中,data经常需要访问listData中的数据,在自定义渲染器中,如果有必要也可以覆盖listData属性来影响data中的取值。
IListItemRendener接口
IListItemRenderer接口是作为单元渲染器的基本要求,IListItemRenderer自身并不定义方法,但它继承了IDataRenderer,IFlexDisplayObject,ILayoutManagerClient,ISimpleStyleClient和IUIComponent接口,IListItemRenderer接口向我们表明单元渲染器至少是一个拥有data属性的UIComponent组件,如果要定义嵌入式单元渲染器,还必须实现IDropInListItemRenderer接口。换句话说,单元渲染器必须有data属性,但不一定要有listData属性,列表组件发现单元渲染器不是IDropInListItemRenderer会略过listData的设置。
使用默认渲染器
默认渲染器是实现IListItemRenderer和IDropInListItemRenderer的UIComponent组件,列表组件的默认渲染器有很多共同的地方,它们显示标签、图标和提示文本中的一种或几种,默认渲染器读取listData中的单元信息,列表组件通过自身属性匹配数据源中的字段写入listData,如下表:
属性
默认值
处理函数
默认值
说明
labelField:String
label
labelFunction:Function
null
文本标签
iconField:String
null
iconFunction:Function
null
图标
dataTipField:String
label
dataTipFunction:Function
null
提示文本
字段属性提供默认值,这表示使用能够定义数据源的情况下将数据源定义为默认的字段名可以省去字段属性的设置,但数据源来自于第三方时必须进行设置,默认渲染器在字段为空时不会显示具体内容,例如iconField为null时不会显示图标。处理函数用于对字段进行加工,例如进行字段组合显示或格式化。
将Flex组件作为渲染器
能够作为渲染器的内置Flex组件有Button,ComboBox,DateField,Image,Label,NumericStepper,TextArea,TextInput,它们都实现IListItemRenderer和IDropInListItemRenderer接口,能够直接使用。使用Flex组件时,如果