ActionScript移动项目组件开发(1):可滚动的容器

内容简介

现在移动开发是个很热的话题,特别是当Adobe从工具到平台都针对移动应用做了很多增强之后,身为ActionScript开发者的我们,是不是已经跃跃欲试了?基于Adobe Flash平台,我们可以开发两种类型的移动项目:ActionScript移动项目和Flex移动项目。基于Flex框架的移动项目,可以使用Flex框架的核心内容以及专为移动设备优化的移动组件和外观,比如按钮,列表等,如果内容的尺寸超出了屏幕的显示范围,还可以用Scroller容器包含该内容,用户可以在移动设备的触摸屏上,用手指和界面交互,控制超出部分内容的显示。如图1,展示了一个Scroller容器包含的地图组件:

p1

图1:Flex项目中可以用Scroller容器处理超出的内容

显然基于Flex框架开发移动应用可以降低我们的开发成本。但是,在一些性能敏感的应用中,Flex框架还是显的太"重"了,所以还会有相当数量的移动项目,我们会基于纯ActionScript进行开发。对于纯ActionScript项目来说,显而易见的好处是,我们有更大的灵活性,更容易优化和控制文件的体积。但是问题也随之而来,对于一些基本的组件和容器等需求,我们也不得不“自己动手,丰衣足食”。虽然网络上已经有一些针对ActionScript项目类型的轻量级UI库,但似乎很难找到对移动设备有完善支持的。

如果有这么一个开源库,那么我们期待它可以包含哪些组件?除了按钮,复选框等这样常见的控件类型,还有我们刚才所说的,对于大尺寸的超出屏幕显示区域的显示对象,应该实现一个类似于Flex中的Scroller这样的容器,来处理超出部分的显示。再联想一下,列表这样的控件跟Scroller的行为非常类似,也是我们非常需要的一个组件。

在这篇文章和下一篇文章中,我们将研讨如何创建两个都具备滚动处理机制的组件:一个是ScrollableContainer,这是一个容器,类似于Sprite,但它可以处理超出内容的显示;一个是ScrollableList,它和Flex中的List组件性质类似,都是根据数据源,展现一个可视和可交互的数据列表。

这些源码已经放在瑞研社区(RIADEV)的开源项目RIASamples中,大家可以用SVN检出所有的源码。

SVN地址:

http://svn.riadev.com/riasamples/trunk/source/riadev-mobile-lib/

前置知识

您需要具备基础的ActionScript 3编程经验,并对于移动项目的特点(屏幕尺寸,交互方式,触碰事件)有一定的了解。

所需软件

您需要下载和安装下面的软件,以便运行这篇文章的相关源码:

实现过程

下面我们就来详细了解如何实现ScrollableContainer这个容器。先来看一个SWF展示,了解它的基本特性(您可以拖动鼠标和它交互):

它的基本特性:

  • 继承自Sprite,是显示对象容器,可以用addChild等方法添加或删除显示列表中的元件
  • 内建遮罩,超出尺寸的部分不予显示,但可以通过鼠标点击或Touch控制内容滚动
  • 内容滚动时,显示一个滚动条(不可交互),指示当前滚动的位置
ScrollableContainer类的实现

首先,它是一个容器,所以继承自Sprite。

  1. public class ScrollableContainer extends Sprite
复制代码

然后,我们考虑应该给它添加哪些属性。对于一个可滚动的容器,我们应该可以设置按水平方向滚动,或按垂直方向滚动,或者两者兼顾。所以增加两个属性,并且是getter和setter形式的:

普通浏览 复制代码
  1. /**@private*/
  2. private var _horizontalScrollEnabled:Boolean= true ;
  3. /**@private*/
  4. private var _verticalScrollEnabled:Boolean= true ;
  5. /**水平可滚动*/
  6. public function get horizontalScrollEnabled ( ):Boolean
  7. {
  8.      return _horizontalScrollEnabled ;
  9. }
  10. public function set horizontalScrollEnabled (value:Boolean ): void
  11. {
  12.     _horizontalScrollEnabled = value ;
  13. }
  14. /**垂直可滚动*/
  15. public function get verticalScrollEnabled ( ):Boolean
  16. {
  17.      return _verticalScrollEnabled ;
  18. }
  19. public function set verticalScrollEnabled (value:Boolean ): void
  20. {
  21.     _verticalScrollEnabled = value ;
  22. }

然后我们要添加一个容器:contentGroup,类型也是Sprite。注意这个容器非常重要,之后我们调用ScrollableContainer的addChild等方法时,实际上是对contentGroup进行操作。我们之所以取消默认的显示列表添加机制,将实际内容放在这样一个子容器里,是为了方便去实现滚动交互,这样我们只控制contentGroup这个容器就可以,而不必循环去让每一个子显示对象都移动位置。

  1. /**添加到内部的显示对象,实际上添加到这个容器中*/
  2. protected var contentGroup:Sprite;
复制代码

然后需要加一个显示对象,但这个显示对象不是用于显示的,而是用做遮罩,超出这个遮罩范围的内容将不会显示出来。如果您之前使用过Flash Professional软件进行动画创作,应该会遮罩这个概念非常熟悉,这在Flash里确实是个很神奇而且很实用的东东;如果您没有Flash Professional的使用体验,对于遮罩可能会产生一些疑惑,这个机制从某些方面来看确实也很怪异,既然只是确定一个显示区域,那应该用Rectangular就可以了,何必多此一举,还要用显示对象呢?个人感觉,这应该是为了兼容在Flash Professional中进行动画创作的流程,在这个流程中遮罩确实是显示对象。关于遮罩的定义和用途,建议您浏览这篇文章,加深理解:

http://help.adobe.com/zh_CN/flash/cs/using/WS9388626D-B940-43f3-87BB-7C3159F5EDE4.html

  1. /**遮罩*/
  2. protected var maskShape:Shape;
复制代码

然后我们需要4个变量,来分别存储:容器的约束尺寸(即用户为组件设置的尺寸),和contengGroup的实际尺寸,注意两者是不一样的,通常情况下contentGroup的实际尺寸要比容器的约束尺寸要大,所以才会有用滚动显示超出内容的需求。

  1. /**容器约束宽度*/
  2. protected var containerWidth:Number=0;
  3. /**容器约束高度*/
  4. protected var containerHeight:Number=0;
  5. /**子元件宽度*/
  6. protected var contentWidth:Number=0;
  7. /**子元件高度*/
  8. protected var contentHeight:Number=0;
复制代码

容器支持简单的样式设置,所以需要一个对象来存储样式的值,在后面的setStyle方法中,会对这个对象进行操作:

普通浏览 复制代码
  1. /**样式对象*/
  2. private var style:Object ;

这里我们还需要增加一个needUpdate的机制,这个机制利用了延迟渲染显示列表的机制(当尺寸变更,样式变更等因素导致需要重新渲染显示列表时,我们只是在代码中加以标记,防止一些重复性的操作导致性能问题(比如我们代码先设置组件宽度,再设置组件高度,那么渲染两次是没有必要的,应该合并为一次渲染)。关于这个机制的解释和详细说明,参见这篇文章。)

  1. /**是否需要更新显示列表*/
  2. public function get needUpdate():Boolean
  3. {
  4. return _needUpdate;
  5. }
  6. public function set needUpdate(value:Boolean):void
  7. {
  8. _needUpdate=value;
  9. if (_needUpdate)
  10. {
  11. if (!hasEventListener(Event.RENDER))
  12. addEventListener(Event.RENDER, updateDisplayList);
  13. if (stage != null)
  14. stage.invalidate();
  15. }
  16. else
  17. {
  18. removeEventListener(Event.RENDER, updateDisplayList);
  19. }
  20. }
复制代码

然后创建一个createChildren方法,并在构造方法中调用。这个方法需要创建容器必须的对象。

  1. /**
  2. * 创建内部所需的对象
  3. */
  4. protected function createChildren():void
  5. {
  6. contentGroup=new Sprite();
  7. super.addChild(contentGroup);
  8. maskShape=new Shape();
  9. super.addChild(maskShape);
  10. contentGroup.mask=maskShape;
  11. style={bgColor: 0xFFFFFF, bgAlpha: 1};
  12. }
复制代码

然后我们需要创建一个measure方法,顾名思义,这个方法的作用就是度量contentGroup的实际尺寸。这是一个性能方面的优化,大家可以看到,在必要的时候会对contentGroup进行度量,然后后面很多的计算方法中将直接使用度量好的值,这将节省一些非必须的性能消耗。类似于这样的方式,在我们尝试对应用的执行性能进行优化的时候,非常有效。

  1. /**
  2. * 计算子元素的尺寸
  3. */
  4. protected function measure():void
  5. {
  6. var startWidth:Number=0;
  7. var startHeight:Number=0;
  8. for (var i:int=0; i < contentGroup.numChildren; i++)
  9. {
  10. var child:DisplayObject=contentGroup.getChildAt(i);
  11. var childPoint:Point=new Point(child.x + child.width, child.y + child.height);
  12. if (childPoint.x > startWidth)
  13. startWidth=childPoint.x;
  14. if (childPoint.y > startHeight)
  15. startHeight=childPoint.y;
  16. }
  17. contentWidth=startWidth;
  18. contentHeight=startHeight;
  19. }
复制代码

这里我们需要思考一个问题,我们创建了一个contentGroup容器,并且希望用户将内容添加到contentGroup容器,而不是ScrollableContainer容器。那么应该提供给用户什么样的接口?ScrollableContainer.contentGroup.addChild() ? 这样会产生一些问题,首先暴露了内部逻辑给用户,给用户造成迷惑,而且用户不一定按照您预想的方式去调用“正确”的方法,他很可能习惯性的仍然使用ScrollableContainer.addChild()。为了避免这些问题,我们决定对用户隐藏细节,用户仍然可以将ScrollableContainer视为一个普通的容器进行操作,但我们内部要override一些方法,以取消默认的行为。

普通浏览 复制代码
  1. /**下面override的这些方法,都是为了更正contentGroup可能导致的错误行为*/
  2.  
  3. /**@private*/
  4. override  public function addChild (child:DisplayObject ):DisplayObject
  5. {
  6.     contentGroup.addChild (child ) ;
  7.     measure ( ) ;
  8.      return child ;
  9. }
  10.  
  11. /**@private*/
  12. override  public function addChildAt (child:DisplayObject, index: int ):DisplayObject
  13. {
  14.     contentGroup.addChildAt (child, index ) ;
  15.     measure ( ) ;
  16.      return child ;
  17. }
  18.  
  19. /**@private*/
  20. override  public function removeChild (child:DisplayObject ):DisplayObject
  21. {
  22.     contentGroup.removeChild (child ) ;
  23.     measure ( ) ;
  24.      return child ;
  25. }
  26.  
  27. /**@private*/
  28. override  public function removeChildAt (index: int ):DisplayObject
  29. {
  30.     var child:DisplayObject=contentGroup.removeChildAt (index ) ;
  31.     measure ( ) ;
  32.      return child ;
  33. }
  34.  
  35. /**@private*/
  36. override  public function setChildIndex (child:DisplayObject, index: int ): void
  37. {
  38.     contentGroup.setChildIndex (child, index ) ;
  39. }
  40.  
  41. /**@private*/
  42. override  public function getChildAt (index: int ):DisplayObject
  43. {
  44.      return contentGroup.getChildAt (index ) ;
  45. }
  46.  
  47. /**@private*/
  48. override  public function getChildByName (name:String ):DisplayObject
  49. {
  50.      return contentGroup.getChildByName (name ) ;
  51. }
  52.  
  53. /**@private*/
  54. override  public function getChildIndex (child:DisplayObject ): int
  55. {
  56.      return contentGroup.getChildIndex (child ) ;
  57. }
  58.  
  59. /**@private*/
  60. override  public function get numChildren ( ): int
  61. {
  62.      return contentGroup.numChildren ;
  63. }

同样我们会重写关于尺寸的定义,我们仍然让用户通过设置width和height来控制容器的尺寸,但这个行为会被我们修改,我们不会直接将这些值设置到容器的尺寸上,而是保存到内部的变量上,并将needUpdate设置为true,在下一次渲染的时候,根据约束尺寸,控制内部容器的显示。如果不更改这个行为,用户对尺寸的定义会让容器产生错误的后果(试想一个100*100的图片,被调整为200*200,是什么效果?显然是整体被放大了)。

普通浏览 复制代码
  1. /**下面对于尺寸的定义,会被容器更改默认行为*/
  2.  
  3. /**@private*/
  4. override  public function get width ( ):Number
  5. {
  6.      return containerWidth ;
  7. }
  8.  
  9. /**@private*/
  10. override  public function set width (value:Number ): void
  11. {
  12.      if  (containerWidth == value )
  13.          return ;
  14.     containerWidth=value ;
  15.     needUpdate= true ;
  16. }
  17.  
  18. /**@private*/
  19. override  public function get height ( ):Number
  20. {
  21.      return containerHeight ;
  22. }
  23.  
  24. /**@private*/
  25. override  public function set height (value:Number ): void
  26. {
  27.      if  (containerHeight == value )
  28.          return ;
  29.     containerHeight=value ;
  30.     needUpdate= true ;
  31. }

我们需要创建一个updateDisplayList方法,当needUpdate设置为true,那么延迟到下一帧,就会调用这个方法对显示列表进行更新:

  1. protected function updateDisplayList(... args):void
  2. {
  3. if (stage == null || width <= 0 || height <= 0)
  4. return;
  5. //background
  6. drawBackground();
  7. //scroller
  8. contentGroup.x=0;
  9. contentGroup.y=0;
  10. }
复制代码

我们还需要考虑到一些意外情况,比如一个loader,加载内容前和加载内容后的尺寸是不一样的,这可能会导致容器的计算错误,所以需要增加一个强制更新的方法,必要的时候调用这个方法强制刷新显示列表:

  1. public function validateNow():void
  2. {
  3. measure();
  4. updateDisplayList();
  5. }
复制代码

我们允许用户设置简单的样式,并且样式的更改应该触发显示列表的更新:

  1. /**
  2. * 获取样式属性的值
  3. * @param styleName
  4. * @return
  5. */
  6. public function getStyle(styleName:String):*
  7. {
  8. return style[styleName];
  9. }
  10. /**
  11. * 设置样式,目前只支持borderColor,bgColor,bgAlpha
  12. * @param styleName
  13. * @param value
  14. */
  15. public function setStyle(styleName:String, value:*):void
  16. {
  17. if (style[styleName] == value)
  18. return;
  19. style[styleName]=value;
  20. needUpdate=true;
  21. }
复制代码

目前的样式支持,只是绘制边框和背景,所以有个绘制背景的方法:

  1. /**
  2. * 绘制背景和边框
  3. */
  4. protected function drawBackground():void
  5. {
  6. var g:Graphics=this.graphics;
  7. g.clear();
  8. if (style["borderColor"] != null)
  9. {
  10. g.lineStyle(1, style["borderColor"], 1);
  11. }
  12. g.beginFill(style["bgColor"], style["bgAlpha"]);
  13. g.drawRect(0, 0, width, height);
  14. g.endFill();
  15. //draw mask
  16. g=maskShape.graphics;
  17. g.clear();
  18. g.beginFill(0x000000, 1);
  19. g.drawRect(1, 1, width - 2, height - 2);
  20. g.endFill();
  21. }
复制代码

做到这一步,您已经可以进行简单的测试了,比如在您的主类里,创建一个ScrollableContaienr的实例,添加一个尺寸较大的图片进去,然后改变浏览器的尺寸,去观察这个容器的行为。类似于下面的语句:

  1. imgContainer = new ScrollableContainer();
  2. imgContainer.setStyle("borderColor",0xCCCCCC);
  3. addChild(imgContainer);
  4. var img:Bitmap = new imgClass();
  5. imgContainer.addChild(img);
  6. imgContainer.width = stage.stageWidth;
  7. imgContainer.height = stage.stageHeight;
复制代码

运行结果:

p2

图2:图片已经应用了遮罩

注意虽然超出的内容已经被隐藏了,但现在还无法对它进行交互。我们把交互单独设计为一个控制类ScrollControler来实现,它不需要是显示对象,来看它的实现过程:

ScrollControler类的实现

类定义:

  1. public class ScrollControler
复制代码

有一些值实际上是从ScrollableContainer拷贝过来,便于计算,重新定义一次:

  1. /**水平可滚动*/
  2. public var horizontalScrollEnabled:Boolean=true;
  3. /**垂直可滚动*/
  4. public var verticalScrollEnabled:Boolean=true;
  5. /**实际内容宽度*/
  6. public var contentWidth:Number = 0;
  7. /**实际内容高度*/
  8. public var contentHeight:Number = 0;
  9. /**组件可用宽度*/
  10. public var parentWidth:Number = 0;
  11. /**组件可用高度*/
  12. public var parentHeight:Number = 0;
复制代码

这里需要传递两个容器,一个是contengGroup,一个是ScrollableContainer本身,比较这两者的尺寸,才能计算出滚动所需的值。

普通浏览 复制代码
  1. /**包含内容的容器*/
  2. private var contentGroup:Sprite ;
  3. /**容器的父级,即ScrollableContainer*/
  4. private var parent:Sprite ;

滚动时,需要知道滚动的方向,和滚动的速度,存在下面的属性中:

普通浏览 复制代码
  1. /**X轴速度*/
  2. private var speedX: int= 0 ;
  3. /**Y轴速度*/
  4. private var speedY: int= 0 ;
  5. /**手指的水平方向,即内容的水平滚动方向*/
  6. private var directionX:String= "left" ;  //or right,手指方向
  7. /**手指的垂直方向,即内容的垂直滚动方向*/
  8. private var directionY:String= "up" ;  //or down,手指方向

启动滚动时的初始速度,是根据光标位置的迁移量,和停顿时间综合计算的,这些值需要保存,以供计算:

普通浏览 复制代码
  1. /**光标坐标点距离起点的位移X*/
  2. private var currentMouseOffsetX:Number= 0 ;
  3. /**光标坐标点距离起点的位移Y*/
  4. private var currentMouseOffsetY:Number= 0 ;
  5. /**启动拖放时的坐标点X*/
  6. private var startX:Number ;
  7. /**启动拖放时的坐标点Y*/
  8. private var startY:Number ;
  9. /**结束拖放时的坐标点X*/
  10. private var endX:Number ;
  11. /**结束拖放时的坐标点Y*/
  12. private var endY:Number ;
  13. /**启动拖放时的时间*/
  14. private var startTime:Number ;
  15. /**结束拖放时的时间*/
  16. private var endTime:Number ;

在构造方法中,将所依赖的两个容器传入。

普通浏览 复制代码
  1. public function ScrollControler (contentGroup:Sprite, parent:Sprite )
  2. {
  3.      this.contentGroup=contentGroup ;
  4.      this.parent=parent ;
  5.     initContainer ( ) ;
  6. }
  7. /**
  8.  * 什么也不做,等待容器添加到显示列表
  9.  */        
  10. private function initContainer ( ): void
  11. {
  12.     container.addEventListener (Event.ADDED_TO_STAGE,addListeners ) ;
  13.     container.addEventListener (Event.REMOVED_FROM_STAGE,clear ) ;
  14. }
  15. /**
  16.  * 当容器添加到显示列表,则注册事件侦听
  17.  * @param event
  18.  */        
  19. protected function addListeners ( event:Event ): void
  20. {
  21.     container.addEventListener (MouseEvent.MOUSE_DOWN, containerMouseHandler ) ;
  22.     container.stage.addEventListener (Event.MOUSE_LEAVE, mouseLeaveHandler ) ;
  23. }

在containerMouseHandler这个方法里面,集中了对于鼠标事件(单点Touch事件会自动进行映射)的判断和处理。实现的过程是:首先捕获到MouseDown事件,这个时候启动拖动,让contentGroup容器跟随用户的光标进行移动,当捕获到MouseUp事件,则根据移动的坐标值和停留时间综合计算,得出X和Y方向上的起始速度,然后通过EnterFrame事件触发的方法执行,去实现动画。

下图展示了这个过程:

p3

图3:触发滚动的过程

普通浏览 复制代码
  1. private function containerMouseHandler ( event:Event ): void
  2. {
  3.      if  ( event.type == MouseEvent.MOUSE_DOWN )
  4.     {
  5.         startX=parent.mouseX ;
  6.         startY=parent.mouseY ;
  7.         startTime=getTimer ( ) ;
  8.         currentMouseOffsetX=parent.mouseX - container.x ;
  9.         currentMouseOffsetY=parent.mouseY - container.y ;
  10.         fllowMouse= true ;
  11.         container.stage.addEventListener (MouseEvent.MOUSE_UP, containerMouseHandler ) ;
  12.         container.addEventListener (Event.ENTER_FRAME, enterFrameHandler ) ;
  13.     }
  14.      else  if  ( event.type == MouseEvent.MOUSE_UP )
  15.     {
  16.         endX=parent.mouseX ;
  17.         endY=parent.mouseY ;
  18.         endTime=getTimer ( ) ;
  19.         var timeOffset:Number=endTime - startTime ;
  20.         directionX= (endX <= startX ) ?  "left" :  "right" ;
  21.         directionY= (endY <= startY ) ?  "up" :  "down" ;
  22.         speedX= (endX - startX )  / (timeOffset /  20 ) ;
  23.         speedY= (endY - startY )  / (timeOffset /  20 ) ;
  24.         currentMouseOffsetX= 0 ;
  25.         currentMouseOffsetY= 0 ;
  26.         fllowMouse= false ;
  27.         checkXRange ( ) ;
  28.         checkYRange ( ) ;
  29.         container.stage.removeEventListener (MouseEvent.MOUSE_UP, containerMouseHandler ) ;
  30.     }
  31. }

动画的实现,实际上是在enterFrameHandler这个方法里完成的。当起始速度被设置,意味着动画开始,然后速度值进行递增或递减,实现缓动效果,滚动会逐渐减速,最终停止。

普通浏览 复制代码
  1. private function enterFrameHandler ( event:Event ): void
  2. {
  3.      if  (fllowMouse )
  4.     {
  5.          if (horizontalScrollEnabled )
  6.             container.x=parent.mouseX - currentMouseOffsetX ;
  7.          if (verticalScrollEnabled )
  8.             container.y=parent.mouseY - currentMouseOffsetY ;
  9.         scrollBarRef.update (container.x,container.y ) ;
  10.          return ;
  11.     }
  12.      if  (Math.abs (speedX ) <  4 )
  13.         speedX= 0 ;
  14.      if  (Math.abs (speedY ) <  4 )
  15.         speedY= 0 ;
  16.      if (speedX ==  0 && speedY ==  0 )
  17.     {
  18.         scrollBarRef.clear ( ) ;
  19.         container.removeEventListener (Event.ENTER_FRAME, enterFrameHandler ) ;
  20.          return ;
  21.     }
  22.     container.x+=speedX ;
  23.     container.y+=speedY ;
  24.     checkXRange ( ) ;
  25.     checkYRange ( ) ;
  26.      if  (directionX ==  "left" )
  27.         speedX+= 1 ;
  28.      else
  29.         speedX-= 1 ;
  30.      if  (directionY ==  "up" )
  31.         speedY+= 1 ;
  32.      else
  33.         speedY-= 1 ;
  34.     scrollBarRef.update (container.x,container.y ) ;
  35. }

在滚动的过程中,需要判断坐标值出界的情况,一旦出界,则停止动画。

普通浏览 复制代码
  1. /**检查X值,确定不超出允许范围*/
  2. private function checkXRange ( ): void
  3. {
  4.      if (contentWidth <= parentWidth )
  5.     {
  6.         speedX= 0 ;
  7.         container.x= 0 ;
  8.          return ;
  9.     }
  10.      if  (container.x + contentWidth <= parentWidth )
  11.     {
  12.         speedX= 0 ;
  13.         container.x=parentWidth - contentWidth ;
  14.          return ;
  15.     }
  16.      if  (container.x >=  0 )
  17.     {
  18.         speedX= 0 ;
  19.         container.x= 0 ;
  20.          return ;
  21.     }
  22. }
  23.  
  24. /**检查Y值,确定不超出允许范围*/
  25. private function checkYRange ( ): void
  26. {
  27.      if (contentHeight <= parentHeight )
  28.     {
  29.         speedY= 0 ;
  30.         container.y= 0 ;
  31.          return ;
  32.     }
  33.      if  (container.y + contentHeight <= parentHeight )
  34.     {
  35.         speedY= 0 ;
  36.         container.y=parentHeight - contentHeight ;
  37.          return ;
  38.     }
  39.      if  (container.y >=  0 )
  40.     {
  41.         speedY= 0 ;
  42.         container.y= 0 ;
  43.          return ;
  44.     }
  45. }

至此这个滚动控制类实现完毕,下面需要和我们刚才创建的ScrollableContainer容器进行整合。

为ScrollableContainer增加滚动交互控制

回到ScrollableContainer类,首先声明变量:

  1. /**滚动控制器*/
  2. protected var scrollControler:ScrollControler;
复制代码

修改createChildren方法,增加实例化的代码:

  1. scrollControler = new ScrollControler(contentGroup,this);
复制代码

修改updateDisplayList方法,将尺寸传递给控制类:

  1. scrollControler.parentWidth = width;
  2. scrollControler.parentHeight = height;
  3. scrollControler.contentWidth = contentWidth;
  4. scrollControler.contentHeight = contentHeight;
复制代码

OK,现在再运行刚才的测试代码,您可以看到容器已经可交互了,图片已经可以随着您的鼠标拖动,而以缓冲的效果进行滚动。现在美中不足的是,没有一个滚动条的显示来明确指出当前的滚动位置,我们可以再创建一个ScrollBar类来实现这个功能。这里不再对这个类的实现进行详细描述,代码中已经对主体部分做了注释:

ScrollBar.as
普通浏览 复制代码
  1. package com.riadev.mobile.ui.support
  2. {
  3.     import com.greensock.TweenLite ;
  4.     
  5.     import flash.display.Graphics ;
  6.     import flash.display.Shape ;
  7.  
  8.      /**
  9.      * 只用于显示滚动位置,不可交互
  10.      * @author NeoGuo
  11.      * 
  12.      */    
  13.      public  class ScrollBar extends Shape
  14.     {
  15.          /**水平可滚动*/
  16.          public var horizontalScrollEnabled:Boolean= true ;
  17.          /**垂直可滚动*/
  18.          public var verticalScrollEnabled:Boolean= true ;
  19.         
  20.          /**实际内容宽度*/
  21.          public var contentWidth:Number =  0 ;
  22.          /**实际内容高度*/
  23.          public var contentHeight:Number =  0 ;
  24.          /**组件可用宽度*/
  25.          public var parentWidth:Number =  0 ;
  26.          /**组件可用高度*/
  27.          public var parentHeight:Number =  0 ;
  28.         
  29.          /**线段与边界的距离*/
  30.          private var paddingValue:Number =  10 ;
  31.          /**线段的粗细程度*/
  32.          private var lineStroke:Number =  10 ;
  33.          /**
  34.          * 构造方法
  35.          */        
  36.          public function ScrollBar ( )
  37.         {
  38.             super ( ) ;
  39.         }
  40.          /**
  41.          * 更新当前的显示图形
  42.          * @param scrollX contentGroup的x坐标
  43.          * @param scrollY contentGroup的y坐标
  44.          */        
  45.          public function update (scrollX:Number,scrollY:Number ): void
  46.         {
  47.             var g:Graphics =  this.graphics ;
  48.             g.clear ( ) ;
  49.              //draw horizontal graphics
  50.              if (horizontalScrollEnabled && contentWidth > parentWidth )
  51.             {
  52.                 g.lineStyle (lineStroke+ 2, 0x000000, 0 .5 ) ;
  53.                 var endY:Number = parentHeight-paddingValue ;
  54.                 g.moveTo (paddingValue,endY ) ;
  55.                 g.lineTo (parentWidth-paddingValue,endY ) ;
  56.                 g.lineStyle (lineStroke, 0xFFFFFF, 1 ) ;
  57.                 var lineWidth:Number =  (parentWidth-2*paddingValue )* (parentWidth/contentWidth ) ;
  58.                 var lineStartX:Number = -scrollX/ (contentWidth-parentWidth )* (parentWidth-2*paddingValue-lineWidth ) + paddingValue ;
  59.                  if (lineStartX<paddingValue )
  60.                 {
  61.                     lineWidth -=  (paddingValue-lineStartX ) ;
  62.                     lineStartX = paddingValue ;
  63.                 }
  64.                  if (lineStartX+lineWidth > parentWidth-paddingValue )
  65.                 {
  66.                     lineWidth -= lineStartX+lineWidth- (parentWidth-paddingValue ) ;
  67.                 }
  68.                 g.moveTo (lineStartX,endY ) ;
  69.                 g.lineTo (lineStartX+lineWidth,endY ) ;
  70.             }
  71.              //draw vertical graphics
  72.              if (verticalScrollEnabled && contentHeight > parentHeight )
  73.             {
  74.                 g.lineStyle (lineStroke+ 2, 0x000000, 0 .5 ) ;
  75.                 var endX:Number = parentWidth-paddingValue ;
  76.                 g.moveTo (endX,paddingValue ) ;
  77.                 g.lineTo (endX,parentHeight-paddingValue ) ;
  78.                 g.lineStyle (lineStroke, 0xFFFFFF, 1 ) ;
  79.                 var lineHeight:Number =  (parentHeight-2*paddingValue )* (parentHeight/contentHeight ) ;
  80.                 var lineStartY:Number = -scrollY/ (contentHeight-parentHeight )* (parentHeight-2*paddingValue-lineHeight ) + paddingValue ;
  81.                  if (lineStartY<paddingValue )
  82.                 {
  83.                     lineHeight -=  (paddingValue-lineStartY ) ;
  84.                     lineStartY = paddingValue ;
  85.                 }
  86.                  if (lineStartY+lineHeight > parentHeight-paddingValue )
  87.                 {
  88.                     lineHeight -= lineStartY+lineHeight- (parentHeight-paddingValue ) ;
  89.                 }
  90.                 g.moveTo (endX,lineStartY ) ;
  91.                 g.lineTo (endX,lineStartY+lineHeight ) ;
  92.             }
  93.         }
  94.          /**
  95.          * 清理
  96.          */        
  97.          public function clear ( ): void
  98.         {
  99.             TweenLite.to ( this, 0 .5,{alpha: 0,onComplete:reset} ) ;
  100.         }
  101.          /**
  102.          * 重置
  103.          */        
  104.          private function reset ( ): void
  105.         {
  106.             var g:Graphics =  this.graphics ;
  107.             g.clear ( ) ;
  108.             alpha =  1 ;
  109.         }
  110.     }
  111. }

现在尝试重新运行测试代码,拖动鼠标,可以看到实时的图形显示:

p4

图4:滚动条效果

在下一篇文章里,我们将继续封装可滚动的List组件。

后续工作

您可以尝试使用这个容器,体验它的特性,当然它的代码刚刚编写完毕,还没有经过大量实践的验证,可能存在一些缺陷和Bug,欢迎您批评和指正。然后您可以继续浏览Adobe开发者中心,寻找移动开发相关的文章和资源,加深这一领域的知识技能。


原文转载:http://www.riadev.com/flex-thread-1206-1-1.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值