贴吧涂鸦–毕加索的画板

摘要

贴吧社区上线了用户等级权限系统,“涂鸦”属于“等级权限”项目中的单项权限功能,有助于丰富完善等级权限体系,为高等级用户提供更强大的功能,帮助产出差异化内容。

“涂鸦”使用Actionscript3开发。本文主要介绍功能实现方式和开发过程中值得注意的地方。

TAG

Actionscript3 画图板

术语或简称

AS: Actionscript3.0;

DOOUM: AS的鼠标五个事件,MouseDown、MouseOver、MouseOut、MouseUp、MouseMove事件;

内容

1.分层设计 — 简化的MVC;

2.单例模式;

3.画笔、橡皮擦和背景的实现;

4.撤销、重做的实现;

分层设计

网络层

CheckJsReady:负责初始化时和JS互相确认初始化完毕;

SayToJs:负责ActionScript和JavaScript的交互工作;

ImageUpload: 负责ActionScript和Server的交互;

应用层

1)    View & Model 因为View层的元素不会出现多例的情况,View和Model层已结合在一起。

ViewList: 保存所有主要View对象引用,单例;

Paper:涂鸦画板;

ToolPanel: 工具栏,放置涂鸦相关工具;

BackgroundPicture: 画板背景对象;

WaterMarker:涂鸦水印;

2)     Control

ControlCore: 控制中心,单例;

分层模型图示(图1)

贴吧涂鸦–毕加索的画板_第1张图片

MVC模型图示(图2)

贴吧涂鸦–毕加索的画板_第2张图片

基本配置文件

BaseConf: 基本配置,包括Flash所有的默认配置;
FunctionalButtonImages: 加载所有的按钮上的Icon图片,默认编译在swf中;
MouseCursorImages: 加载所有的鼠标指针,默认编译在swf中;
MsgList: 消息文本列表,程序所有的提醒消息文本。

单例模式

单例模式可以不用在多个类中产生实例,而只是产生一个实例。单例模式可以实现在多个类中可以共享一个实例的数据。同时也可以避免在多个类中反复创建某个实例的工作,因为实际上我们并不需要也不想要每次都new一遍。

如前面所述,ControlCore和 ViewList均使用的是单例模式。

ControlCore作为控制中心,应该是以一个全局的对象存在。所以它不应该在每个类中都出现一个实例。

ViewList保存的是主要View层对象的引用,其实质是对象引用的List,也应该是一个全局的对象。

在单例模式中,使用单例模式的类应该是无法被实例化的。这样才能保证这个类的正确使用。一般将构造函数定义成私有的(private)即可达到这个目的。但是在AS中,构造函数是无法定义成私有的。所以在AS选择了另一种做法。

例: 以下是 A.as代码

01 Package {
02        public class A{
03        static private var _a:A;
04 //构造函数需要传入Class N的实例
05     public function A(n:N){}
06  
07     public static function g():A{
08     if(A._a == null) {
09         A._a = new A(new N());
10 }
11 return A._a;
12 }
13  
14 public function doSomething():void{}
15  }
16 }
17  
18 //在package外再定义一个类N
19 Class N{}

说明:
类A的构造函数需要传入类N的实例,但是类N是在A.as中定义的,只有在这个文件才能被访问。这样就保证了,类A的构造函数在A.as以外是无法正常被调用的,类似于构造函数私有化了。
在类A中声明一个私有的属性-类A静态对象,该属性将会在g()方法第一次被调用的时候被定义。g()方法的返回是一个类A的实例化对象,他有类A的所有的属性和方法。所以类A的其他公共方法都可以通过g()的返回值来调用。
这种方式还有一种好处是在g()没有被调用之前,私有的_a是不会被定义的。这样可以不用一开始就占用资源。同样,不把类中A所有方法定义成静态方法也是这个原因。
如 doSomething方法 可以这样调用 A.g().dosomething()。
通过这种形式,在AS中也能很方便的使用单例模式。

画笔、橡皮擦和背景

涂鸦最主要的逻辑实现都集中在Paper上。

一是因为Paper是画纸,是用户主要操作区域;

二是因为当初设计时,没有再进行细分,一些附属的功能也加在Paper里。

画笔

其实实现画笔很简单,监听好鼠标的DOOUM五个事件即可。

在实现上,Paper只是一个容器,装载着已画好的图像和正在画图像。并提供给ControlCore一个提取图像数据的接口当做提交之用。

背景BackgroundPicture处于Paper之下。当用户选择加载本地的图片之后,显示该图片。

大致的层次关系如(图3)所示:

贴吧涂鸦–毕加索的画板_第3张图片

如图所示,在用户开始绘画的时候(触发Paper的MouseDown事件),Paper则会创建一个和自己一般大小的A。Paper通过监听用户鼠标事件获得的鼠标轨迹数据,A通过接口获得数据,并draw出。

由于一些原因,在鼠标快速移入移出Paper的时候会导致笔迹与画纸边界出现断裂的现象。通过监听Paper的MouseOver & MouseOut,在触发这两个事件的时候获取鼠标触发以上两个事件的坐标,在A中进行一些偏差容错的计算,使得笔迹连贯自然。(不过应该有更好的方式来实现)

用户画完后,触发Paper的MouseUp事件。在此时将A的数据与B的数据进行合并,同时将A移除。用户看到的就是最新画好的图像了。

橡皮擦

橡皮擦,可以看做是一种比较独特的画笔。记得以前有一种笔,笔迹是透明的,而且可以把笔迹经过的地方的其他的颜色抹去,很类似这里的橡皮擦。

其实橡皮擦只是在draw的模式上有所区别。

亦如上图。

默认情况下,A的blendMode为BlendMode.Normal,在两层数据合并时的模式也是一样。

用户选择橡皮擦之后, A的BlendMode则被定义为BlendMode.LAYER,在两层数据合并时的模式改为BlendMode. ERASE。

这样用户用橡皮擦“画笔”画过的地方就变成了透明的。

撤销、重做

在最开始设计的时候,思维形成了定式。从表面上看每次撤销和重做,都是回滚或者回退用户操作的某一笔。程序需要操作的是某一笔的信息—笔迹的坐标记录。

实际实现中发现,这样不靠谱。因为一笔可以无限长,这样就会导致撤销和重做会都抖需要遍历一个长度不可控的数组。即便是使用Vector,时间和空间的复杂度都是单位计量*N。

通过参考其他的绘画应用,使用了以下方式来实现撤销和重做,使得时间和空间都变得可控:

为撤销和重做自定义两个固定长度的“队列”。同时插入B的图像初始数据对象(BitmapData)入撤销队列。

在每次A与B的数据合并之后(参考图3),将B的数据对象的副本插入撤销队列。

当用户撤销时将队尾的数据对象弹出并插入重做队列中,再将此时的顶部数据对象显示在B中。

当用户重做时将重做队尾数据对象弹出并插入撤销队列中,并将对象显示在B中。

具体如图 4.1 – 4.3所示,

贴吧涂鸦–毕加索的画板_第4张图片

图4.1 画好的图像压入撤销栈

贴吧涂鸦–毕加索的画板_第5张图片

图4.2 撤销
贴吧涂鸦–毕加索的画板_第6张图片
4.3 重做

从图中可以看出,撤销队列中始终是需要有数据对象的,而且当前队尾的数据对象和当前显示的图像数据对象一致。当确定了撤销的步数为N的之后,那么撤销队列的最大值则为N+1,而重做队列的最大值则为N。
撤销队列满了时,将队头的数据对象弹出并销毁。




原文来源: http://stblog.baidu-tech.com/?p=1312

你可能感兴趣的:(JavaScript,mvc,vector,Class,工具,actionscript)