现在的位置: 首页 > 综合 > 正文

flex4.5组件库使用:定制IconItemRenderer

2013年09月09日 ⁄ 综合 ⁄ 共 10586字 ⁄ 字号 评论关闭

许多Flex HERO的移动应用例子详细介绍了如何制作RSS Reader,使用的是Spark List和LabelItemRenderer或者IconItemRenderer。由于使用的都是默认的IconItemRenderer,这些应用看起来都一个样事实上,通过继承IconItemRenderer,定制自己的Item Renderer组件,我们能够设计出多种多样的应用。本文将主要讲解如何定制化自己的IconItemRenderer。

本文基于一个Engadget新闻阅读器移动应用EngadgetAIR,使用Engadget的RSS(http://cn.engadget.com/rss)作为新闻的数据源,通过HttpService获取,并显示在一个Spark列表中。当然,我们要使用定制的IconItemRenderer:EngadgetItemRenderer。

关于ICONITEMRENDERER

默认的IconItemRenderer

图1: 默认的IconItemRenderer

IconItemRenderer (注:在早前的Flex HE?RO 预览版中的MobileIconItemRenderer已经被重命名为IconItemRenderer)是HERO新加入的item render(所谓的"列表项呈示器",如果你能明白中文翻译说的是啥的话),并且为移动应用做了优化。

在IconItemRenderer中,开发者可以指定title, message, icon和decorator属性。上图显示了在Spark列表中应用的默认IconItemRenderer。 IconItemRenderer应用了Flex SDK HERO中提供的许多新特性。比如,在Flex HERO中,BitmapImage引入了新的缩放功能。 通过设置新引入的 'scaleMode' 属性,开发者可以方便地配置扩展图像、填充内容区的方式。在我们接下来看到的例子中,将使用iconScaleMode="letterbox",以等比例的方式缩放显示图像。

使用SPARK LIST生成显示ENGADGET新闻的列表

使用Spark List创建新闻列表News.mxml

  • News.mxml是Flex移动应用EngadgetAIR(在整理完毕后,我会将该应用的源码共享在本博中)中的一个视图(如果你不了解HERO移动应用开发架构,请参考Adobe Devnet中相关教程)。在该视图中,我们通过应用的Singleton类AppPM获取engaget RSS数据(即AppPM.getInstance.rss)。rss.items是包含了新闻列表的ArrayCollection,将被绑定在Spark List类型的新闻列表newsList的dataprovider(数据提供者)。
  • 我们创建了封装IconItemRenderer(或者我们自定义的itemRenderer)的NewsItemRenderer组件,该组件负责呈现列表中的每条新闻。

封装Item Renderer的MXML组件NewsItemRenderer.mxml

    • NewsItemRenderer.mxml实际上是一个继承自我们自定义Item renderer(EngadgetItemRenderer)的MXML组件(当然你也可以修改为继承默认IconItemRenderer或者LabelItemRenderer)。
    • 一些值得注意的属性:
      • labelField、messageField、iconField和decorator分别定义了将要在item中显示的标签、描述、图标和装饰图标。
        • iconScaleMode:Spark IconItemRenderer使用了BitmapImage处理icon,iconScaleMode定义了一个image的缩放行为。iconScaleMode有两个属性值BitmapScaleMode.STRETCH("stretch")和BitmapScaleLETTERBOX(letterbox)。设置为"stretch"时,图片将被拉伸填充,而如果设置为"letterbox",图表将按照原始尺寸等比例调整填充。具体内容可以参见http://opensource.adobe.com/wiki/display/flexsdk/Spark+Image*
        • iconPlaceholder:在加载外部图片资源时,有时候会希望显示一个默认图片。一旦设置iconPlaceholder,加载外部图片时,IconItemRenderer就会在icon位置显示iconPlaceholder指定的图片对象。

      定制IconItemRenderer: EngadgetItemRenderer.as

      创建继承IconItemRenderer类的EngadgetItemRenderer.as。

      组件的生命周期

      在开始创建自定义Item Renderer类EngadgetItemRenderer之前,我们需要解释一下Flex组件的生命周期。所有UIComponent的子类,都遵从同样的生命周期,Flex框架通过自动调用createChildren()、commitProperties()、measure()和updateDisplayList()方法来管理组件。

      Flex调用createChildren()在组件自身内部创建子组件,但是对于一些数据驱动的组件或者动态组件(这些组件随着生命周期的变化其属性,比如尺寸属性,会发生变化),通常会延迟(defer)在之后的commitProperties()方法中创建。比如在EnadgetItemRenderer中,createChildren()方法绘制了黑色半透明矩形rectShape,LabelItemRenderer中,createChildren()方法创建了label子组件。然而,在IconItemRenderer中,icon、message和decorator这些子组件都被延迟在commitProperties()方法中创建。开发者根据具体情况来判断在哪个方法中创建子组件。在计算组件的尺寸(measure方法)和进行组件布局(updateDisplayList)之前,Flex框架会调用commitProperties()方法,这个方法用来计算并把变化的值设定给属性以及相关数据。在commitProperties方法中,我们也会根据属性值的变化销毁需要移除的子组件。在属性发生变化时。我们通常会调用invalidateSize()(对应measure方法)和invalidateDispalyList()(对应updateDisplayList)方法,通知Flex框架来调用measure方法或者updateDisplayList()方法。Flex框架会根据具体情况来决定何时调用。measure方法用来计算组件的"自然"尺寸和最小尺寸,当组件的子组件尺寸发生变化时,Flex框架也会隐式的调用该方法。而updateDisplayList则根据组件的布局规则计算各组件位置并布局。

      我们以IconItemRenderer为例,看看这些方法完成的主要工作:

      • createChildren():IconItemRenderer的createChildren方法实际上没有做任何事情,只是调用了父组件LabelItemRenderer的createChildren方法。其子组件icon、message和decorator都被延迟到commitProperties方法中完成创建。至于label子组件,由于IconItemRenderer继承自LabelItemRenderer,因此其由LabelItemRenderer的createChildren方法创建。
      • commitProperties():IconItemRenderer在commitProperties方法中,分别调用了createMessageDispaly()、createIconDispaly()和createDecoratorDisplay()创建了需要显示的message、icon和decorator子组件。
      • measure():计算这些需要显示子组件的尺寸,组件的尺寸会根据显示内容(比如图片的大小、message内容)的不同发生变化。
      • updateDisplayList():调用父类LabelItemRenderer的drawBackground()绘制背景,之后调用layoutContents()方法设定label、icon、decorator和message的尺寸和位置,完成布局。

      定制IconItemRenderer的步骤

      我们自定义IconItemRenderer实现后的效果如下图。

      自定义IconItemRenderer

      图2: 自定义IconItemRenderer

      为了实现该效果,定制EngadgetItemRenderer.as需要完成如下工作:

      • 创建EngadgetItemRenderer类,继承IconItemRenderer
      • 重新布局;
      • 绘制一个半透明的黑色背景区域来衬托白色的message
      • 加入图片下载进度条

      下面我们逐一讲解。

      重新布局:覆盖layoutContents方法

      IconItemRenderer中,子组件的布局是在layoutContents()方法中完成的。为了重新布局,我们在EngadgetItemRenderer类中覆盖该方法,实现自定义布局。由于在我们的例子中,新的item render只显示icon、decorator和message,因此我们简化了layoutContents()方法。

      下面列出了完整的layoutContents()方法,具体解释见代码中注释。

      1. override protected function layoutContents(unscaledWidth:Number,unscaledHeight:Number):void
      2. {
      3. // no need to call super.layoutContents() since we're changing how it happens here
      4. // start laying out our children now
      5. var myIconWidth:Number = 0;
      6. var myIconHeight:Number = 0;
      7. var decoratorWidth:Number = 0;
      8. var decoratorHeight:Number = 0;
      9. var decoratorY:Number=0;
      10. var decoratorX:Number=0;
      11. var messageWidth:Number = 0;
      12. var messageHeight:Number = 0;
      13. var messageY:Number=0;
      14. var hasMessage:Boolean = messageDisplay && messageDisplay.text != "";
      15. var paddingLeft:Number = getStyle("paddingLeft");
      16. var paddingRight:Number = getStyle("paddingRight");
      17. var paddingTop:Number = getStyle("paddingTop");
      18. var paddingBottom:Number = getStyle("paddingBottom");
      19. var horizontalGap:Number = getStyle("horizontalGap");
      20. var verticalGap:Number = (hasMessage) ? getStyle("verticalGap") : 5;
      21. if (iconDisplay)
      22. {
      23. this.iconWidth=unscaledWidth;
      24. // 设置图标的尺寸:宽度与手机屏幕同宽。由于我们设置iconDisplay为letterbox,因此图片会自动等比例缩放
      25. setElementSize(iconDisplay, this.iconWidth, this.iconHeight);
      26. myIconWidth = iconDisplay.getLayoutBoundsWidth(); //实际上,myIconWidth就是item的宽度
      27. myIconHeight = iconDisplay.getLayoutBoundsHeight(); //myIconHeight就是item的高度
      28. //设置图片的位置,x=0,y=0>
      29. setElementPosition(iconDisplay, 0, 0);
      30. }
      31. // decorator的位置居中,靠右
      32. if (decoratorDisplay)
      33. {
      34. decoratorWidth = getElementPreferredWidth(decoratorDisplay);
      35. decoratorHeight = getElementPreferredHeight(decoratorDisplay);
      36. //设定decorator的尺寸
      37. setElementSize(decoratorDisplay, decoratorWidth, decoratorHeight);
      38. // decorator居中,靠右
      39. decoratorX=unscaledWidth - decoratorWidth;
      40. decoratorY = Math.round((unscaledHeight - decoratorHeight)/2) ;
      41. setElementPosition(decoratorDisplay, decoratorX, decoratorY);
      42. }
      43. // 计算message的位置和尺寸。message同图片同宽,居中靠下。
      44. messageWidth=myIconWidth;
      45. var messageTextHeight:Number = 0;
      46. if (hasMessage)
      47. {
      48. // commit styles to make sure it uses updated look
      49. messageDisplay.commitStyles();
      50. }
      51. if (hasMessage)
      52. {
      53. // handle message...because the text is multi-line, measuring and layout
      54. // can be somewhat tricky
      55. messageWidth = Math.max(messageWidth, 0);
      56. // We get called with unscaledWidth = 0 a few times...
      57. // rather than deal with this case normally,
      58. // we can just special-case it later to do something smarter
      59. if (messageWidth == 0)
      60. {
      61. // if unscaledWidth is 0, we want to make sure messageDisplay is invisible.
      62. // we could set messageDisplay's width to 0, but that would cause an extra
      63. // layout pass because of the text reflow logic. Because of that, we
      64. // can just set its height to 0.
      65. setElementSize(messageDisplay, NaN, 0);
      66. }
      67. else
      68. {
      69. // 在resize之前,获取messageDisplay的现有高度
      70. var oldPreferredMessageHeight:Number = getElementPreferredHeight(messageDisplay);
      71. // 记住现有的item宽度
      72. oldUnscaledWidth = unscaledWidth;
      73. // 设置message的尺寸,message比屏幕宽度小paddingLeft,作为边距(此处可以设置新的style来允许定制)
      74. setElementSize(messageDisplay, messageWidth-paddingLeft-paddingRight, oldPreferredMessageHeight);</p>
      75. //在message已经确定最终宽度后,获取其最终高度。
      76. var newPreferredMessageHeight:Number = getElementPreferredHeight(messageDisplay);
      77. // 测试宽度改变后,message文本重新布局得到的最终高度与原来高度是否相同,如果不同,则需要调度measure()重新计算item的尺寸
      78. if (oldPreferredMessageHeight != newPreferredMessageHeight)
      79. invalidateSize();
      80. //记录获取到的message
      81. messageHeight = newPreferredMessageHeight;
      82. }
      83. //设置message的位置:居中,靠下但留下verticalGap大小的下边距
      84. messageY=unscaledHeight-messageHeight-verticalGap;
      85. setElementPosition(messageDisplay,paddingLeft,messageY);
      86. //设置message黑色背景尺寸
      87. rectHeight=messageHeight+verticalGap *2;
      88. }
      89. }

      绘制半透明黑色背景

      接下来,我们希望能够在message文字后面填充半透明黑色背景,来更清晰地衬托显示文字。这部分工作将要在createChildren()方法中完成。

      注:

      尽管我们在layoutComponents之后覆盖createChildren方法,但其实该方法在layoutComponents方法(由updateDisplayList方法调用,想想我们刚刚讲过的组件生命周期)之前执行。

      覆盖createChildren()方法的代码如下:

      1. override protected function createChildren():void
      2. {
      3. if(!rectShape){
      4. rectShape=new Shape();
      5. rectShape.visible=false;
      6. rectShape.graphics.beginFill(0x000000,0.7);
      7. rectShape.graphics.drawRect(0,0,1,1); //此处暂不指定真实尺寸和位置
      8. this.addChild(rectShape);
      9. }
      10. super.createChildren();
      11. }

      如上代码所示,我们绘制了一个半透明黑色矩形,但是并没有制定其具体尺寸和位置。尺寸和位置需要在updateDisplayList()方法中完成,因为其依赖与其他子组件(即message和icon)的尺寸。

      我们在上面的layoutContents()方法的尾部加入如下代码,完成半透明黑色矩形最终的尺寸设定和的位置布局。

      1. if(rectShape &amp;&amp; rectHeight>0){
      2. rectShape.width=myIconWidth;
      3. rectShape.height=rectHeight;
      4. rectShape.x=0;
      5. rectShape.y=unscaledHeight-rectShape.height;
      6. rectShape.visible=true
      7. }

      加入图片下载进度指示

      EngadgetItemRenderer中的图片下载进度指示

      图3:EngadgetItemRenderer 中的图片下载进度指示

      在图片下载过程中,IconItemRenderer会显示iconPlaceholder属性指定的嵌入图片对象(如果指定了iconPlaceholder的话)。但是IconItemRenderer并没有显示图片的下载状态,对于移动应用来说,由于网络连接速度的限制,有的时候应用会因此显得似乎没有响应。接下来,我们将为自定义的EngadgetItemRenderer加入下载进度指示。

      我们的进度指示组件是一个继承了UIComponent的as3类,我们不在这里介绍如何制作进度指示,下面代码中ProgressBar即为该进度指示组件,该组件将接收要显示下载进度的BitmapImage对象(本例中就是iconDisplay,负责显示icon),通过bytesLoaded和bytesTotal属性,以及BitmapImage对象的PROGRESS类型的ProgressEvent事件与READY类型的FlexEvent事件管理下载进度显示。

      这里需要解决的是,如何在EngadgetItemRenderer中添加ProgressBar,并及时销毁。

      在自定义的EngadgetItemRenderer中,我们并没有在createChildren()方法中创建progressBar。这是由于Spark List组件并不是为List中的每一个项目生成一个IconItemRenderer实例,当滚动屏幕时,List会重用已经创建的

      IconItemRenderer绘制对应的Item。因此,如果我们在createChildren方法中创建progressBar,就会漏掉那些被重用的Item Render(因为Flex框架不会调用createChildren方法来创建已经被销毁的progressBar)。这些Item就不会显示下载进度指示。

      IcomRenderer使用了createIconDisplay()方法来创建icon,使用destroyIconDisplay()方法来销毁icon。我们就借助这两个方法在其中为icon加入或者删除PROGRESS类型的ProgressEvent事件侦听器onIconDisplayProgress方法及READY类型的FlexEvent事件侦听器onIconDisplayReady方法。在onIconDisplayProgress方法中,每当图片开始加载,我们就实例化progressBar,并将其加入显示列表displayList。在onIconDisplayReady方法中,在下载完成后,我们就从displayList中删除该progressBar。

      完成代码如下:

      1. // 创建icon,并添加事件侦听器以创建下载显示进度指示
      2. override protected function createIconDisplay():void{
      3. super.createIconDisplay();
      4. iconDisplay.addEventListener(ProgressEvent.PROGRESS,onIconDisplayProgress);
      5. iconDisplay.addEventListener(FlexEvent.READY,onIconDisplayReady);
      6. }
      7. // 创建icon,并删除事件侦听器
      8. override protected function destroyIconDisplay():void{
      9. super.destroyIconDisplay();
      10. if(progressBar &amp;&amp; progressBar.parent){
      11. removeChild(progressBar)
      12. progressBar=null;
      13. }
      14. iconDisplay.removeEventListener(ProgressEvent.PROGRESS,onIconDisplayProgress);
      15. iconDisplay.removeEventListener(FlexEvent.READY,onIconDisplayReady);
      16. }
      17. // 如果没有下载指示,则创建下载进度指示。这里需要判断再次添加侦听器,因为iconDisplay可能被重用,所以对应的侦听器事件已被删除
      18. private function onIconDisplayProgress(event:ProgressEvent):void{
      19. if(!progressBar){
      20. progressBar = new ProgressBar(iconDisplay);
      21. addChild(progressBar);
      22. if(iconDisplay &amp;&amp; !iconDisplay.hasEventListener(FlexEvent.READY)){
      23. iconDisplay.addEventListener(ProgressEvent.PROGRESS,onIconDisplayProgress);
      24. iconDisplay.addEventListener(FlexEvent.READY,onIconDisplayReady);
      25. }
      26. }
      27. }
      28. // 删除下载进度指示
      29. private function onIconDisplayReady(event:FlexEvent):void{
      30. if(progressBar &amp;&amp; progressBar.parent){
      31. removeChild(progressBar);
      32. progressBar=null;
      33. }
      34. }

      小结

      到此为止,我们已经创建为EngadgetAIR应用基于IconItemRenderer创建了新的EngadgetItemRenderer。希望能你通过这个例子能够更好的理解IconItemRenderer以及Flex组件的生命周期。

      本例来自于正在开发的一个Flex移动应用示例EngadgetAIR,在完成第一阶段的全部开发工作之后,我会把该应用和源码分享在这个博客以及我的新浪微博中。如果您希望了解更多,可以关注我的微博和博客。

      ownload: EngadgetMobileAIR.fxp

      NOTES: 这个应用还没有完成,代码仅供学习参考。我会持续更新这个应用。

      关于作者

      flex4.5组件库使用:定制IconItemRenderer - wolfgangkiefer - 南海群岛

      董龙飞

      http://t.sina.com.cn/donglongfei

       

      zhuanzi:http://wolfgangkiefer.blog.163.com/blog/static/86265503201151873812258/

【上篇】
【下篇】

抱歉!评论已关闭.