ActionScript移动项目组件开发(1):可滚动的容器
内容简介
现在移动开发是个很热的话题,特别是当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。
- public class ScrollableContainer extends Sprite
复制代码
然后,我们考虑应该给它添加哪些属性。对于一个可滚动的容器,我们应该可以设置按水平方向滚动,或按垂直方向滚动,或者两者兼顾。所以增加两个属性,并且是getter和setter形式的:
- /**@private*/
- private var _horizontalScrollEnabled:Boolean=true;
- /**@private*/
- private var _verticalScrollEnabled:Boolean=true;
- /**水平可滚动*/
- public function get horizontalScrollEnabled():Boolean
- {
- return _horizontalScrollEnabled;
- }
- public function set horizontalScrollEnabled(value:Boolean):void
- {
- _horizontalScrollEnabled = value;
- }
- /**垂直可滚动*/
- public function get verticalScrollEnabled():Boolean
- {
- return _verticalScrollEnabled;
- }
- public function set verticalScrollEnabled(value:Boolean):void
- {
- _verticalScrollEnabled = value;
- }
然后我们要添加一个容器:contentGroup,类型也是Sprite。注意这个容器非常重要,之后我们调用ScrollableContainer的addChild等方法时,实际上是对contentGroup进行操作。我们之所以取消默认的显示列表添加机制,将实际内容放在这样一个子容器里,是为了方便去实现滚动交互,这样我们只控制contentGroup这个容器就可以,而不必循环去让每一个子显示对象都移动位置。
- /**添加到内部的显示对象,实际上添加到这个容器中*/
- 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
- /**遮罩*/
- protected var maskShape:Shape;
复制代码
然后我们需要4个变量,来分别存储:容器的约束尺寸(即用户为组件设置的尺寸),和contengGroup的实际尺寸,注意两者是不一样的,通常情况下contentGroup的实际尺寸要比容器的约束尺寸要大,所以才会有用滚动显示超出内容的需求。
- /**容器约束宽度*/
- protected var containerWidth:Number=0;
- /**容器约束高度*/
- protected var containerHeight:Number=0;
- /**子元件宽度*/
- protected var contentWidth:Number=0;
- /**子元件高度*/
- protected var contentHeight:Number=0;
复制代码
容器支持简单的样式设置,所以需要一个对象来存储样式的值,在后面的setStyle方法中,会对这个对象进行操作:
- /**样式对象*/
- private var style:Object;
这里我们还需要增加一个needUpdate的机制,这个机制利用了延迟渲染显示列表的机制(当尺寸变更,样式变更等因素导致需要重新渲染显示列表时,我们只是在代码中加以标记,防止一些重复性的操作导致性能问题(比如我们代码先设置组件宽度,再设置组件高度,那么渲染两次是没有必要的,应该合并为一次渲染)。关于这个机制的解释和详细说明,参见这篇文章。)
- /**是否需要更新显示列表*/
- public function get needUpdate():Boolean
- {
- return _needUpdate;
- }
- public function set needUpdate(value:Boolean):void
- {
- _needUpdate=value;
- if (_needUpdate)
- {
- if (!hasEventListener(Event.RENDER))
- addEventListener(Event.RENDER, updateDisplayList);
- if (stage != null)
- stage.invalidate();
- }
- else
- {
- removeEventListener(Event.RENDER, updateDisplayList);
- }
- }
复制代码
然后创建一个createChildren方法,并在构造方法中调用。这个方法需要创建容器必须的对象。
- /**
- * 创建内部所需的对象
- */
- protected function createChildren():void
- {
- contentGroup=new Sprite();
- super.addChild(contentGroup);
- maskShape=new Shape();
- super.addChild(maskShape);
- contentGroup.mask=maskShape;
- style={bgColor: 0xFFFFFF, bgAlpha: 1};
- }
复制代码
然后我们需要创建一个measure方法,顾名思义,这个方法的作用就是度量contentGroup的实际尺寸。这是一个性能方面的优化,大家可以看到,在必要的时候会对contentGroup进行度量,然后后面很多的计算方法中将直接使用度量好的值,这将节省一些非必须的性能消耗。类似于这样的方式,在我们尝试对应用的执行性能进行优化的时候,非常有效。
- /**
- * 计算子元素的尺寸
- */
- protected function measure():void
- {
- var startWidth:Number=0;
- var startHeight:Number=0;
- for (var i:int=0; i < contentGroup.numChildren; i++)
- {
- var child:DisplayObject=contentGroup.getChildAt(i);
- var childPoint:Point=new Point(child.x + child.width, child.y + child.height);
- if (childPoint.x > startWidth)
- startWidth=childPoint.x;
- if (childPoint.y > startHeight)
- startHeight=childPoint.y;
- }
- contentWidth=startWidth;
- contentHeight=startHeight;
- }
复制代码
这里我们需要思考一个问题,我们创建了一个contentGroup容器,并且希望用户将内容添加到contentGroup容器,而不是ScrollableContainer容器。那么应该提供给用户什么样的接口?ScrollableContainer.contentGroup.addChild() ? 这样会产生一些问题,首先暴露了内部逻辑给用户,给用户造成迷惑,而且用户不一定按照您预想的方式去调用“正确”的方法,他很可能习惯性的仍然使用ScrollableContainer.addChild()。为了避免这些问题,我们决定对用户隐藏细节,用户仍然可以将ScrollableContainer视为一个普通的容器进行操作,但我们内部要override一些方法,以取消默认的行为。
- /**下面override的这些方法,都是为了更正contentGroup可能导致的错误行为*/
- /**@private*/
- override public function addChild(child:DisplayObject):DisplayObject
- {
- contentGroup.addChild(child);
- measure();
- return child;
- }
- /**@private*/
- override public function addChildAt(child:DisplayObject, index:int):DisplayObject
- {
- contentGroup.addChildAt(child, index);
- measure();
- return child;
- }
- /**@private*/
- override public function removeChild(child:DisplayObject):DisplayObject
- {
- contentGroup.removeChild(child);
- measure();
- return child;
- }
- /**@private*/
- override public function removeChildAt(index:int):DisplayObject
- {
- var child:DisplayObject=contentGroup.removeChildAt(index);
- measure();
- return child;
- }
- /**@private*/
- override public function setChildIndex(child:DisplayObject, index:int):void
- {
- contentGroup.setChildIndex(child, index);
- }
- /**@private*/
- override public function getChildAt(index:int):DisplayObject
- {
- return contentGroup.getChildAt(index);
- }
- /**@private*/
- override public function getChildByName(name:String):DisplayObject
- {
- return contentGroup.getChildByName(name);
- }
- /**@private*/
- override public function getChildIndex(child:DisplayObject):int
- {
- return contentGroup.getChildIndex(child);
- }
- /**@private*/
- override public function get numChildren():int
- {
- return contentGroup.numChildren;
- }
同样我们会重写关于尺寸的定义,我们仍然让用户通过设置width和height来控制容器的尺寸,但这个行为会被我们修改,我们不会直接将这些值设置到容器的尺寸上,而是保存到内部的变量上,并将needUpdate设置为true,在下一次渲染的时候,根据约束尺寸,控制内部容器的显示。如果不更改这个行为,用户对尺寸的定义会让容器产生错误的后果(试想一个100*100的图片,被调整为200*200,是什么效果?显然是整体被放大了)。
- /**下面对于尺寸的定义,会被容器更改默认行为*/
- /**@private*/
- override public function get width():Number
- {
- return containerWidth;
- }
- /**@private*/
- override public function set width(value:Number):void
- {
- if (containerWidth == value)
- return;
- containerWidth=value;
- needUpdate=true;
- }
- /**@private*/
- override public function get height():Number
- {
- return containerHeight;
- }
- /**@private*/
- override public function set height(value:Number):void
- {
- if (containerHeight == value)
- return;
- containerHeight=value;
- needUpdate=true;
- }
我们需要创建一个updateDisplayList方法,当needUpdate设置为true,那么延迟到下一帧,就会调用这个方法对显示列表进行更新:
- protected function updateDisplayList(... args):void
- {
- if (stage == null || width <= 0 || height <= 0)
- return;
- //background
- drawBackground();
- //scroller
- contentGroup.x=0;
- contentGroup.y=0;
- }
复制代码
我们还需要考虑到一些意外情况,比如一个loader,加载内容前和加载内容后的尺寸是不一样的,这可能会导致容器的计算错误,所以需要增加一个强制更新的方法,必要的时候调用这个方法强制刷新显示列表:
- public function validateNow():void
- {
- measure();
- updateDisplayList();
- }
复制代码
我们允许用户设置简单的样式,并且样式的更改应该触发显示列表的更新:
- /**
- * 获取样式属性的值
- * @param styleName
- * @return
- */
- public function getStyle(styleName:String):*
- {
- return style[styleName];
- }
- /**
- * 设置样式,目前只支持borderColor,bgColor,bgAlpha
- * @param styleName
- * @param value
- */
- public function setStyle(styleName:String, value:*):void
- {
- if (style[styleName] == value)
- return;
- style[styleName]=value;
- needUpdate=true;
- }
复制代码
目前的样式支持,只是绘制边框和背景,所以有个绘制背景的方法:
- /**
- * 绘制背景和边框
- */
- protected function drawBackground():void
- {
- var g:Graphics=this.graphics;
- g.clear();
- if (style["borderColor"] != null)
- {
- g.lineStyle(1, style["borderColor"], 1);
- }
- g.beginFill(style["bgColor"], style["bgAlpha"]);
- g.drawRect(0, 0, width, height);
- g.endFill();
- //draw mask
- g=maskShape.graphics;
- g.clear();
- g.beginFill(0x000000, 1);
- g.drawRect(1, 1, width - 2, height - 2);
- g.endFill();
- }
复制代码
做到这一步,您已经可以进行简单的测试了,比如在您的主类里,创建一个ScrollableContaienr的实例,添加一个尺寸较大的图片进去,然后改变浏览器的尺寸,去观察这个容器的行为。类似于下面的语句:
- imgContainer = new ScrollableContainer();
- imgContainer.setStyle("borderColor",0xCCCCCC);
- addChild(imgContainer);
- var img:Bitmap = new imgClass();
- imgContainer.addChild(img);
- imgContainer.width = stage.stageWidth;
- imgContainer.height = stage.stageHeight;
复制代码
运行结果:
图2:图片已经应用了遮罩
注意虽然超出的内容已经被隐藏了,但现在还无法对它进行交互。我们把交互单独设计为一个控制类ScrollControler来实现,它不需要是显示对象,来看它的实现过程:
ScrollControler类的实现
类定义:
- public class ScrollControler
复制代码
有一些值实际上是从ScrollableContainer拷贝过来,便于计算,重新定义一次:
- /**水平可滚动*/
- public var horizontalScrollEnabled:Boolean=true;
- /**垂直可滚动*/
- public var verticalScrollEnabled:Boolean=true;
- /**实际内容宽度*/
- public var contentWidth:Number = 0;
- /**实际内容高度*/
- public var contentHeight:Number = 0;
- /**组件可用宽度*/
- public var parentWidth:Number = 0;
- /**组件可用高度*/
- public var parentHeight:Number = 0;
复制代码
这里需要传递两个容器,一个是contengGroup,一个是ScrollableContainer本身,比较这两者的尺寸,才能计算出滚动所需的值。
- /**包含内容的容器*/
- private var contentGroup:Sprite;
- /**容器的父级,即ScrollableContainer*/
- private var parent:Sprite;
滚动时,需要知道滚动的方向,和滚动的速度,存在下面的属性中:
- /**X轴速度*/
- private var speedX:int=0;
- /**Y轴速度*/
- private var speedY:int=0;
- /**手指的水平方向,即内容的水平滚动方向*/
- private var directionX:String="left"; //or right,手指方向
- /**手指的垂直方向,即内容的垂直滚动方向*/
- private var directionY:String="up"; //or down,手指方向
启动滚动时的初始速度,是根据光标位置的迁移量,和停顿时间综合计算的,这些值需要保存,以供计算:
- /**光标坐标点距离起点的位移X*/
- private var currentMouseOffsetX:Number=0;
- /**光标坐标点距离起点的位移Y*/
- private var currentMouseOffsetY:Number=0;
- /**启动拖放时的坐标点X*/
- private var startX:Number;
- /**启动拖放时的坐标点Y*/
- private var startY:Number;
- /**结束拖放时的坐标点X*/
- private var endX:Number;
- /**结束拖放时的坐标点Y*/
- private var endY:Number;
- /**启动拖放时的时间*/
- private var startTime:Number;
- /**结束拖放时的时间*/
- private var endTime:Number;
在构造方法中,将所依赖的两个容器传入。
- public function ScrollControler(contentGroup:Sprite, parent:Sprite)
- {
- this.contentGroup=contentGroup;
- this.parent=parent;
- initContainer();
- }
- /**
- * 什么也不做,等待容器添加到显示列表
- */
- private function initContainer():void
- {
- container.addEventListener(Event.ADDED_TO_STAGE,addListeners);
- container.addEventListener(Event.REMOVED_FROM_STAGE,clear);
- }
- /**
- * 当容器添加到显示列表,则注册事件侦听
- * @param event
- */
- protected function addListeners(event:Event):void
- {
- container.addEventListener(MouseEvent.MOUSE_DOWN, containerMouseHandler);
- container.stage.addEventListener(Event.MOUSE_LEAVE, mouseLeaveHandler);
- }
在containerMouseHandler这个方法里面,集中了对于鼠标事件(单点Touch事件会自动进行映射)的判断和处理。实现的过程是:首先捕获到MouseDown事件,这个时候启动拖动,让contentGroup容器跟随用户的光标进行移动,当捕获到MouseUp事件,则根据移动的坐标值和停留时间综合计算,得出X和Y方向上的起始速度,然后通过EnterFrame事件触发的方法执行,去实现动画。
下图展示了这个过程:
图3:触发滚动的过程
- private function containerMouseHandler(event:Event):void
- {
- if (event.type == MouseEvent.MOUSE_DOWN)
- {
- startX=parent.mouseX;
- startY=parent.mouseY;
- startTime=getTimer();
- currentMouseOffsetX=parent.mouseX - container.x;
- currentMouseOffsetY=parent.mouseY - container.y;
- fllowMouse=true;
- container.stage.addEventListener(MouseEvent.MOUSE_UP, containerMouseHandler);
- container.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
- }
- else if (event.type == MouseEvent.MOUSE_UP)
- {
- endX=parent.mouseX;
- endY=parent.mouseY;
- endTime=getTimer();
- var timeOffset:Number=endTime - startTime;
- directionX=(endX <= startX) ? "left" : "right";
- directionY=(endY <= startY) ? "up" : "down";
- speedX=(endX - startX) / (timeOffset / 20);
- speedY=(endY - startY) / (timeOffset / 20);
- currentMouseOffsetX=0;
- currentMouseOffsetY=0;
- fllowMouse=false;
- checkXRange();
- checkYRange();
- container.stage.removeEventListener(MouseEvent.MOUSE_UP, containerMouseHandler);
- }
- }
动画的实现,实际上是在enterFrameHandler这个方法里完成的。当起始速度被设置,意味着动画开始,然后速度值进行递增或递减,实现缓动效果,滚动会逐渐减速,最终停止。
- private function enterFrameHandler(event:Event):void
- {
- if (fllowMouse)
- {
- if(horizontalScrollEnabled)
- container.x=parent.mouseX - currentMouseOffsetX;
- if(verticalScrollEnabled)
- container.y=parent.mouseY - currentMouseOffsetY;
- scrollBarRef.update(container.x,container.y);
- return;
- }
- if (Math.abs(speedX) < 4)
- speedX=0;
- if (Math.abs(speedY) < 4)
- speedY=0;
- if(speedX == 0 && speedY == 0)
- {
- scrollBarRef.clear();
- container.removeEventListener(Event.ENTER_FRAME, enterFrameHandler);
- return;
- }
- container.x+=speedX;
- container.y+=speedY;
- checkXRange();
- checkYRange();
- if (directionX == "left")
- speedX+=1;
- else
- speedX-=1;
- if (directionY == "up")
- speedY+=1;
- else
- speedY-=1;
- scrollBarRef.update(container.x,container.y);
- }
在滚动的过程中,需要判断坐标值出界的情况,一旦出界,则停止动画。
- /**检查X值,确定不超出允许范围*/
- private function checkXRange():void
- {
- if(contentWidth <= parentWidth)
- {
- speedX=0;
- container.x=0;
- return;
- }
- if (container.x + contentWidth <= parentWidth)
- {
- speedX=0;
- container.x=parentWidth - contentWidth;
- return;
- }
- if (container.x >= 0)
- {
- speedX=0;
- container.x=0;
- return;
- }
- }
- /**检查Y值,确定不超出允许范围*/
- private function checkYRange():void
- {
- if(contentHeight <= parentHeight)
- {
- speedY=0;
- container.y=0;
- return;
- }
- if (container.y + contentHeight <= parentHeight)
- {
- speedY=0;
- container.y=parentHeight - contentHeight;
- return;
- }
- if (container.y >= 0)
- {
- speedY=0;
- container.y=0;
- return;
- }
- }
至此这个滚动控制类实现完毕,下面需要和我们刚才创建的ScrollableContainer容器进行整合。
为ScrollableContainer增加滚动交互控制
回到ScrollableContainer类,首先声明变量:
- /**滚动控制器*/
- protected var scrollControler:ScrollControler;
复制代码
修改createChildren方法,增加实例化的代码:
- scrollControler = new ScrollControler(contentGroup,this);
复制代码
修改updateDisplayList方法,将尺寸传递给控制类:
- scrollControler.parentWidth = width;
- scrollControler.parentHeight = height;
- scrollControler.contentWidth = contentWidth;
- scrollControler.contentHeight = contentHeight;
复制代码
OK,现在再运行刚才的测试代码,您可以看到容器已经可交互了,图片已经可以随着您的鼠标拖动,而以缓冲的效果进行滚动。现在美中不足的是,没有一个滚动条的显示来明确指出当前的滚动位置,我们可以再创建一个ScrollBar类来实现这个功能。这里不再对这个类的实现进行详细描述,代码中已经对主体部分做了注释:
ScrollBar.as
- package com.riadev.mobile.ui.support
- {
- import com.greensock.TweenLite;
- import flash.display.Graphics;
- import flash.display.Shape;
- /**
- * 只用于显示滚动位置,不可交互
- * @author NeoGuo
- *
- */
- public class ScrollBar extends Shape
- {
- /**水平可滚动*/
- public var horizontalScrollEnabled:Boolean=true;
- /**垂直可滚动*/
- public var verticalScrollEnabled:Boolean=true;
- /**实际内容宽度*/
- public var contentWidth:Number = 0;
- /**实际内容高度*/
- public var contentHeight:Number = 0;
- /**组件可用宽度*/
- public var parentWidth:Number = 0;
- /**组件可用高度*/
- public var parentHeight:Number = 0;
- /**线段与边界的距离*/
- private var paddingValue:Number = 10;
- /**线段的粗细程度*/
- private var lineStroke:Number = 10;
- /**
- * 构造方法
- */
- public function ScrollBar()
- {
- super();
- }
- /**
- * 更新当前的显示图形
- * @param scrollX contentGroup的x坐标
- * @param scrollY contentGroup的y坐标
- */
- public function update(scrollX:Number,scrollY:Number):void
- {
- var g:Graphics = this.graphics;
- g.clear();
- //draw horizontal graphics
- if(horizontalScrollEnabled && contentWidth > parentWidth)
- {
- g.lineStyle(lineStroke+2,0x000000,0.5);
- var endY:Number = parentHeight-paddingValue;
- g.moveTo(paddingValue,endY);
- g.lineTo(parentWidth-paddingValue,endY);
- g.lineStyle(lineStroke,0xFFFFFF,1);
- var lineWidth:Number = (parentWidth-2*paddingValue)*(parentWidth/contentWidth);
- var lineStartX:Number = -scrollX/(contentWidth-parentWidth)*(parentWidth-2*paddingValue-lineWidth) + paddingValue;
- if(lineStartX<paddingValue)
- {
- lineWidth -= (paddingValue-lineStartX);
- lineStartX = paddingValue;
- }
- if(lineStartX+lineWidth > parentWidth-paddingValue)
- {
- lineWidth -= lineStartX+lineWidth-(parentWidth-paddingValue);
- }
- g.moveTo(lineStartX,endY);
- g.lineTo(lineStartX+lineWidth,endY);
- }
- //draw vertical graphics
- if(verticalScrollEnabled && contentHeight > parentHeight)
- {
- g.lineStyle(lineStroke+2,0x000000,0.5);
- var endX:Number = parentWidth-paddingValue;
- g.moveTo(endX,paddingValue);
- g.lineTo(endX,parentHeight-paddingValue);
- g.lineStyle(lineStroke,0xFFFFFF,1);
- var lineHeight:Number = (parentHeight-2*paddingValue)*(parentHeight/contentHeight);
- var lineStartY:Number = -scrollY/(contentHeight-parentHeight)*(parentHeight-2*paddingValue-lineHeight) + paddingValue;
- if(lineStartY<paddingValue)
- {
- lineHeight -= (paddingValue-lineStartY);
- lineStartY = paddingValue;
- }
- if(lineStartY+lineHeight > parentHeight-paddingValue)
- {
- lineHeight -= lineStartY+lineHeight-(parentHeight-paddingValue);
- }
- g.moveTo(endX,lineStartY);
- g.lineTo(endX,lineStartY+lineHeight);
- }
- }
- /**
- * 清理
- */
- public function clear():void
- {
- TweenLite.to(this,0.5,{alpha:0,onComplete:reset});
- }
- /**
- * 重置
- */
- private function reset():void
- {
- var g:Graphics = this.graphics;
- g.clear();
- alpha = 1;
- }
- }
- }
现在尝试重新运行测试代码,拖动鼠标,可以看到实时的图形显示:
图4:滚动条效果
在下一篇文章里,我们将继续封装可滚动的List组件。
后续工作
您可以尝试使用这个容器,体验它的特性,当然它的代码刚刚编写完毕,还没有经过大量实践的验证,可能存在一些缺陷和Bug,欢迎您批评和指正。然后您可以继续浏览Adobe开发者中心,寻找移动开发相关的文章和资源,加深这一领域的知识技能。
原文转载:http://www.riadev.com/flex-thread-1206-1-1.html
ActionScript移动项目组件开发(1):可滚动的容器相关推荐
- ActionScript移动项目组件开发(2):可滚动的容器
内容简介 这篇文章是上一篇< ActionScript移动项目组件开发(1):可滚动容器 >的延续.在上一篇文章中,我们继承Sprite,封装了一个可滚动的容器,并且这个容器的交互方式是适 ...
- Vue项目首页-热销推荐组件开发(7-6)
热销推荐组件开发 创建分支 在项目目录下右键打开gitbase. git pull //从线上将项目拉下来. git checkout index-recommend //进入index-recomm ...
- 基于 next.js + mdx 搭建组件库文档项目(一) -- 开发环境搭建
说明 之前使用过 Docz 来作为组件库文档搭建工具,它基于 gatsby , 提供了高度的定制化能力,但是截止 2021-06-22, Docz 停留在 v2.3.1(2020-04-05) 已经一 ...
- 在Flutter项目中开发IOS桌面组件(WidgetExtension)
在Flutter项目中开发IOS桌面组件(WidgetExtension) 具体的WidgetExtension的开发流程这里就不细说了,可以参考文末的链接. 在Flutter项目开发IOSWidge ...
- COM组件开发实践(八)---多线程ActiveX控件和自动调整ActiveX控件大小(下)
源代码下载:MyActiveX20081229.rar 声明:本文代码基于CodeProject的文章<A Complete ActiveX Web Control Tutorial>修改 ...
- python项目开发实战网盘-《Python项目案例开发从入门到实战》PDF版百度网盘
「教程分享:Python项目开发从入门到实列」 本书例子具有实用性,20个不同类型的完整列子,600分钟高品质配套教学视频,完整的源码和教学课件,让你对枯燥的Python语言学习充满乐趣. 编辑推荐 ...
- react复习总结(1)--react组件开发基础
这次是年后第一次发文章,也有很长一段时间没有写文章了.准备继续写.总结是必须的. 最近一直在业余时间学习和复习前端相关知识点,在一个公司呆久了,使用的技术不更新,未来真的没有什么前景,特别是我们这种以 ...
- React Native组件开发指南
React Native的组件开发一直处在一个比较尴尬的处境.在官方未给予相关示例与脚手架的情况下,社区中依然诞生了许许多多的React Native组件.因为缺少示例与规范,很多组件库仅含有一个in ...
- 从零开始的 React 组件开发之路 (一):表格篇
React 下的表格狂想曲 0. 前言 欢迎大家阅读「从零开始的 React 组件开发之路」系列第一篇,表格篇.本系列的特色是从 需求分析.API 设计和代码设计 三个递进的过程中,由简到繁地开发一个 ...
最新文章
- LeetCode简单题之重新排列数组
- 优先级反转和解决方法
- 为何要清除浮动?如何清除?
- 在数据库中, 不用max()/min()找出一个列中最大/最小值的记录
- vim的一些基本应用
- TTL电平与CMOS电平
- 综合后端各种类型文件
- 《深入剖析Android系统》第9章RIL补充配图
- 在华为能拿多少工资,揭秘一个真实的华为!
- 试试Live Witer
- 深度点击率预估模型的One-Epoch过拟合现象剖析
- 2020年8月中国编程语言排行榜
- 路由器功能及构成——网络层
- android 录屏功能,Android开发如何实现录屏小功能
- Java8中字符串连接(join)收集器 Collectors.joining
- 后序遍历的非递归算法(C 详细)
- (JAVASwing界面)java实现简单的人事管理系统(数据库原理课程设计)
- STM32串口通信代码正确串口却没反应
- 1000G素材资源大礼包,里面包含设计软件、设计师字体包,PS教程笔刷等。
- cc9.3 indesign_InDesign CC实战从入门到精通(全彩版)