AIR文件拖拽的实质
首先让我们来探究一下AIR拖拽中的本质,如下图所示,处于本地系统中的AIR应用,和系统之间通过拖拽方式产生的数据通讯,实际上是通过剪切板进行的。剪切板作为一个中介,如果你拖拽一个文件到AIR应用,那么实质上是将这个文件放到了剪切板,然后AIR应用会从剪切板中获取数据;反之也是如此,当你把文件或数据从AIR应用中拖拽到文件系统中,系统也会根据剪切板中的文件类型决定下一步的操作(比如保存文件)。
拖拽的三个阶段
我们可以把整个拖拽的过程分为三个阶段:启动,拖动和放下。
- 启动:用户通过按住鼠标按键从组件或组件中的项目进行拖动,启动拖放操作。涉及的事件包括nativeDragStart 和nativeDragComplete。启动方式是调取NativeDragManager.doDrag(),这个方法调取后,会启动拖拽的过程。
- 拖动:用户在按住鼠标按键的同时,将鼠标光标移至其他组件、应用程序,或移至桌面。涉及的事件包括nativeDragUpdate,nativeDragEnter,nativeDragOver,nativeDragExit。其中nativeDragEnter事件会被我们用于检测数据格式,以决定是否允许接受该数据格式。如果格式合法,我们可以调用NativeDragManager.acceptDragDrop()方法,允许该数据在组件上放下。这个时候屏幕上光标的形状会有所变化。
- 放下:用户在符合条件的放置目标上释放鼠标。如果是在AIR应用内部放下,涉及的事件包括nativeDragDrop,我们可以捕获这个事件,来获取剪切板中的数据,如果放下的区域在AIR外部,那么将会由操作系统来确定如何处理该操作。
下图描述了NativeDragEvent的各种类型,不同颜色代表这是由不同的对象调度的。
AIR还提供了一个ClipboardFormats类,定义了各种不同的剪切板描述格式,参见下图(这是AIR1.5的版本,在AIR2中增加了一个新的格式,稍后叙述):
按照数据的来源和去处,我们可以将拖拽分为两种类别:拖出和拖入。
拖出操作
拖出的执行过程:
- 创建Clipboard对象包裹要传输的数据
- 触发mouseDown事件,启动拖拽NativeDragManager.doDrag()
- 完成拖拽传输
拖出的代码示例:
bd. draw (myLoader ); var jpg :JPEGEncoder = new JPEGEncoder ( ); file = File.documentsDirectory.resolvePath ( "image.jpg" ); var fileStream :FileStream = new FileStream ( ); fileStream. open (file, FileMode.WRITE ); fileStream. writeBytes (ba, 0,ba. length ); fileStream. close ( ); var transferObject :Clipboard = createClipboard (display ); NativeDragManager.doDrag ( this, transferObject, display. bitmapData, new Point ( - mouseX, - mouseY ) ); } var transfer :Clipboard = new Clipboard ( ); transfer.setData ( "bitmap", image, true ); transfer.setData (ClipboardFormats.BITMAP_FORMAT, image. bitmapData, false ); return transfer; }
该代码的执行过程是,首先有一个正在播放的动画,我用Bitmap获取其中的像素数据,然后用JPEGEncoder编码为JPEG图像文件数据,生成临时文件(这段代码是AIR1.5版本,在AIR2中我们不再需要生成临时文件,而直接使用FilePromise机制)。
拖入操作
拖出的执行过程:
- 将一个 Clipboard 对象拖到一个组件上方。
- 调度 nativeDragEnter 事件。
- 检查数据格式,如果可用,调用 NativeDragManager.acceptDragDrop()。
- NativeDragManager 更改鼠标光标,以指示可以放置此对象。
- 用户将此对象放在此组件上。
- 接收组件调度 nativeDragDrop 事件。
- 接收组件从事件对象内的 Clipboard 对象中读取所需格式的数据。
拖入的代码示例:
private function initApp ( ) : void { this. addEventListener (NativeDragEvent.NATIVE_DRAG_ENTER,onDragIn ); //通常做拖入文件的类型检查 this. addEventListener (NativeDragEvent.NATIVE_DRAG_DROP,onDrop ); //拖拽完成事件 } private function onDragIn (event :NativeDragEvent ) : void { var transferable :Clipboard = event.clipboard; if (transferable.hasFormat (ClipboardFormats.FILE_LIST_FORMAT ) && event.clipboard.getData (ClipboardFormats.FILE_LIST_FORMAT ) [ 0 ].nativePath. split ( "." ) [ 1 ] == "jpg" ) { NativeDragManager.acceptDragDrop ( this ); } else { Alert. show ( "不接受的格式" ); } } private function onDrop (event :NativeDragEvent ) : void { var currentFile :File = dropfiles [ 0 ]; imageSource = currentFile. url; }
该代码的执行过程是,当拖拽目标进入,判断是否合法,如果可以接收该格式,则调取NativeDragManager.acceptDragDrop()。在onDrop方面里,则取得了剪切板中的数据。
AIR2.0中的拖拽增强
AIR1.5的缺憾
- 不能拖拽一个不存在的文件
- 如果你需要将数据拖拽到桌面,需要先保存为临时文件,然后操作临时文件。
AIR2.0的增强
- 引入了File Promise的概念
- 你现在可以从AIR2.0创建的应用中拖拽一个不存在的文件(或位于Server上的文件),并且在松开鼠标后提供这个文件的数据
- 在ClipboardFormats类中,增加了FILE_PROMISE_LIST_FORMAT,这个专门用于File Promise对象
File Promise简介
File Promise 是这样一种机制,我们可以简单理解为信用卡机制,你先承诺给系统,肯定会提供一个文件,当操作系统接收文件的时候,你再把文件的数据写入。这样我们就省略了要创建一个临时文件的步骤。
在AIR中,并没有一个直接的FilePromise类,而是提供了一个IFilePromise的接口。就是说,只要实现这个接口的类,就具备了File Promise的功能。这对于初学者有些不便,不像File类那样直接就可以使用,不过这也提供了更大的扩展性。你可以为你的各种数据实现File Promise功能。
AIR内部只为这个接口实现了一个类:URLFilePromise,顾名思义,这个类就是帮助你将远程URL的文件保存到本地的。它有两个必须的属性:request定义你要发送的请求(通常是WEB上的一个文件地址),relativePath则定义了要保存的文件名。
下面是一段简单的使用URLFilePromise的代码:
private function doDrag ( ) : void { var cb :Clipboard = new Clipboard ( ); var fp :URLFilePromise = new URLFilePromise ( ); fp.request = req; fp.relativePath = item. name; promises. push (fp ); } cb.setData (ClipboardFormats.FILE_PROMISE_LIST_FORMAT, promises ); NativeDragManager.doDrag (lt, cb ); }
注意代码中使用了数组,这意味着可以允许用户同时拖拽多个远程文件到本地。这已经为实现类似FTP应用桌面端扫清了障碍。
下面我们来探讨实现自定义的File Promise功能类。
自定义File Promise功能类
实现IFilePromise接口
如果你想使用File Promise机制但不能使用URLFilePromise,可以在自定义类中实现该接口。注意数据源是支持同步和异步的,具体罗列如下:
- ByteArray (同步)
- FileStream (同步或异步)
- Socket (异步)
- URLStream (异步)
实现该接口,必须实现的方法:
- open():IDataInput 返回数据源对象,这个对象必须实现IDataInput接口,如果是异步,还必须实现IEventDispatcher
- get relativePath():String 提供一个包含文件名称的路径
- get isAsync():Boolean 数据是同步还是异步
- close():void 当数据读取完毕或错误时关闭
- reportError( e:ErrorEvent ):void 异常时抛出错误
自定义FilePromise类实例
package com.riameeting.promise { import flash.desktop.IFilePromise; import mx. graphics.codec.JPEGEncoder; public class BitmapFilePromise implements IFilePromise { { bmp = b; } { return filePath; } { return false; } { var jpg :JPEGEncoder = new JPEGEncoder ( ); ba. position = 0; return ba; } public function close ( ) : void { } } }
bd. draw (myLoader ); var fp :BitmapFilePromise = new BitmapFilePromise (display ); var cb :Clipboard = new Clipboard ( ); cb.setData (ClipboardFormats.FILE_PROMISE_LIST_FORMAT, [fp ] ); NativeDragManager.doDrag (myLoader, cb ); }