内容简介

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

图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编程经验,并对于移动项目的特点(屏幕尺寸,交互方式,触碰事件)有一定的了解。

所需软件

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

  • Flash Builder 4.5 | 下载试用

实现过程

下面我们就来详细了解如何实现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;

复制代码

运行结果:

图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事件触发的方法执行,去实现动画。

下图展示了这个过程:

图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. }

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

图4:滚动条效果

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

后续工作

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

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

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

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

    内容简介 这篇文章是上一篇< ActionScript移动项目组件开发(1):可滚动容器 >的延续.在上一篇文章中,我们继承Sprite,封装了一个可滚动的容器,并且这个容器的交互方式是适 ...

  2. Vue项目首页-热销推荐组件开发(7-6)

    热销推荐组件开发 创建分支 在项目目录下右键打开gitbase. git pull //从线上将项目拉下来. git checkout index-recommend //进入index-recomm ...

  3. 基于 next.js + mdx 搭建组件库文档项目(一) -- 开发环境搭建

    说明 之前使用过 Docz 来作为组件库文档搭建工具,它基于 gatsby , 提供了高度的定制化能力,但是截止 2021-06-22, Docz 停留在 v2.3.1(2020-04-05) 已经一 ...

  4. 在Flutter项目中开发IOS桌面组件(WidgetExtension)

    在Flutter项目中开发IOS桌面组件(WidgetExtension) 具体的WidgetExtension的开发流程这里就不细说了,可以参考文末的链接. 在Flutter项目开发IOSWidge ...

  5. COM组件开发实践(八)---多线程ActiveX控件和自动调整ActiveX控件大小(下)

    源代码下载:MyActiveX20081229.rar 声明:本文代码基于CodeProject的文章<A Complete ActiveX Web Control Tutorial>修改 ...

  6. python项目开发实战网盘-《Python项目案例开发从入门到实战》PDF版百度网盘

    「教程分享:Python项目开发从入门到实列」 本书例子具有实用性,20个不同类型的完整列子,600分钟高品质配套教学视频,完整的源码和教学课件,让你对枯燥的Python语言学习充满乐趣. 编辑推荐 ...

  7. react复习总结(1)--react组件开发基础

    这次是年后第一次发文章,也有很长一段时间没有写文章了.准备继续写.总结是必须的. 最近一直在业余时间学习和复习前端相关知识点,在一个公司呆久了,使用的技术不更新,未来真的没有什么前景,特别是我们这种以 ...

  8. React Native组件开发指南

    React Native的组件开发一直处在一个比较尴尬的处境.在官方未给予相关示例与脚手架的情况下,社区中依然诞生了许许多多的React Native组件.因为缺少示例与规范,很多组件库仅含有一个in ...

  9. 从零开始的 React 组件开发之路 (一):表格篇

    React 下的表格狂想曲 0. 前言 欢迎大家阅读「从零开始的 React 组件开发之路」系列第一篇,表格篇.本系列的特色是从 需求分析.API 设计和代码设计 三个递进的过程中,由简到繁地开发一个 ...

最新文章

  1. LeetCode简单题之重新排列数组
  2. 优先级反转和解决方法
  3. 为何要清除浮动?如何清除?
  4. 在数据库中, 不用max()/min()找出一个列中最大/最小值的记录
  5. vim的一些基本应用
  6. TTL电平与CMOS电平
  7. 综合后端各种类型文件
  8. 《深入剖析Android系统》第9章RIL补充配图
  9. 在华为能拿多少工资,揭秘一个真实的华为!
  10. 试试Live Witer
  11. 深度点击率预估模型的One-Epoch过拟合现象剖析
  12. 2020年8月中国编程语言排行榜
  13. 路由器功能及构成——网络层
  14. android 录屏功能,Android开发如何实现录屏小功能
  15. Java8中字符串连接(join)收集器 Collectors.joining
  16. 后序遍历的非递归算法(C 详细)
  17. (JAVASwing界面)java实现简单的人事管理系统(数据库原理课程设计)
  18. STM32串口通信代码正确串口却没反应
  19. 1000G素材资源大礼包,里面包含设计软件、设计师字体包,PS教程笔刷等。
  20. cc9.3 indesign_InDesign CC实战从入门到精通(全彩版)

热门文章

  1. tensorflow中gradients基本用法
  2. 什么是动画和角色设计的3D索具?3D建模知识科普,下篇
  3. 解决jsbridge在原生app上页面调用两次的问题
  4. Permutation Forger(思维)
  5. 艾德克斯充电测试软件_艾德克斯车载充电机测试解决方案
  6. python趣味编程-高速公路汽车游戏
  7. Galaxy S3刷机改内核的流水账(1)
  8. Gitlab--安装及汉化
  9. python synonyms 近义词
  10. 利用post-data来构造信息抓取艺龙酒店