[原创 flex] flex制作的多文件上传组件
效果演示地址:http://www.adanghome.com/upload/
源文件下载地址:http://www.adanghome.com/flash_upload.rar
下载后导入flexbuilder,即可按需修改。
最近在研究flex,这方面资料很少。从《程序员》杂志上看到《flex第一步》出版的时候,超高兴,周末就跑去买了。只是因为工作太忙,一直没机会学。现在项目已经基本完成了,我总算有时间来研究它了。
项目中需要增加一个flash上传的组件,实现多文件上传,同时显示上传进度。样式要像flickr或者海内一样。首先,我想到了去网上down一个。结果找来找去也没找到好用的,而且就算找到了,改变样式等等又会是麻烦的事情。算了,还是自己做一个吧。因为是第一次使用flex,也是第一次使用as3,所以这个组件用了一周的时间才完成。
先放上代码吧。
这是主mxml:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="495" height="380" fontSize="12" backgroundAlpha="0" applicationComplete="initApp()">
<mx:Script>
<![CDATA[
import mx.events.IndexChangedEvent;
import mx.events.CloseEvent;
import flash.net.FileReference;
import flash.net.FileReferenceList;
import flash.net.FileFilter;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.net.URLRequest;
import mx.controls.Alert;
import mx.events.CloseEvent;
import flash.external.ExternalInterface;
public var fileRef:FileReferenceList = new FileReferenceList();
public var uploadImg_num:int = 0;
public var upload_size:Number = 0;
public var upload_size_total:Number = 0;
private var info:Array = new Array();
//===================
// 功能设置
//===================
/* 允许一次性上传的最大图片数 ********************/
private var upload_maxCount:int = 50;
/* 上传提交的服务器端文件路径 ********************/
private var severURL:String = "photos.php?op=add_photo&tag=abc&album=0&gid=0&party_id=0&attr=0";
/* 服务器端接收用的名称 ********************/
private var fileName:String = "file1";
public var urlRequest:URLRequest = new URLRequest(severURL);
//===================
// 浏览本地文件
//===================
internal function browseHandler(e:Event):void{
var imageTypes:FileFilter = new FileFilter("Images(*.jpg,*.jpeg,*.gif,*.png)","*.jpg;*.jpeg;*.gif;*.png");
var allTypes:Array = new Array(imageTypes);
fileRef.browse(allTypes);
}
//===================
// 选中文件,关闭对话框
//===================
internal function selectHandler(e:Event):void{
var imageNum:int = fileRef.fileList.length;
if(imageNum>upload_maxCount){
Alert.show("一次最多只能上传"+upload_maxCount+"张图片","提示信息");
return;
}
info = new Array();
var sizeMum:Number = 0;
delete_btn.enabled = true;
add_btn.enabled = true;
for(var i:Number=0;i<imageNum;i++){
info.push({label:fileRef.fileList[i].name,data:fileRef.fileList[i].size/1000+"KB",width:0});
sizeMum+=fileRef.fileList[i].size;
}
process_list.dataProvider = info;
tip_txt.text="共"+imageNum+"张图片,"+(sizeMum/(1000*1000)).toString().slice(0,-4)+"MB";
}
//===================
// 单个文件上传结束
//===================
internal function completeHandler():void{
fileRef.fileList[uploadImg_num].removeEventListener(ProgressEvent.PROGRESS,onProcessHandler);
fileRef.fileList[uploadImg_num].removeEventListener(Event.COMPLETE,onComplete);
upload_size+=fileRef.fileList[uploadImg_num].size;
processBar_Total.width=(upload_size/upload_size_total)*488;
tip_txt.text="已上传" + int(upload_size*100/upload_size_total) + "% (" + (uploadImg_num+1) +"/" + fileRef.fileList.length + ")";
uploadImg_num++;
process_list.scrollToIndex(uploadImg_num);
if(uploadImg_num>=fileRef.fileList.length){
// ExternalInterface.call("uploadCompelete");
return;
}
upLoadImg(uploadImg_num);
}
internal function onComplete(e:Event):void{
completeHandler();
// Alert.show(e.toString());
}
//===================
// 监听上传进度
//===================
internal function onProcessHandler(e:ProgressEvent):void{
var proc:uint = e.bytesLoaded/e.bytesTotal*100;
var num:Number = uploadImg_num;
if(process_list.indexToItemRenderer(num)!=null)
{
process_list.indexToItemRenderer(num).document.processBar.width = proc*0.01*245;
info[num].width = proc*0.01*245;
}
}
//===================
// 执行上传操作
//===================
internal function upLoadImg(oNum:int):void{
try
{
fileRef.fileList[oNum].upload(urlRequest,fileName);
fileRef.fileList[oNum].addEventListener(DataEvent.UPLOAD_COMPLETE_DATA,onComplete);
fileRef.fileList[oNum].addEventListener(ProgressEvent.PROGRESS,onProcessHandler);
}
catch(e:Event){
Alert.show("上传出错","提示信息");
completeHandler();
}
}
//===================
// 点击上传按钮
//===================
internal function uploadHandler():void{
if(uploadImg_num!=0) return;
if(process_list.dataProvider==null || info.length<=0){
Alert.show("您还未选择图片!","提示信息");
return;
}
for(var i:Number=0;i<fileRef.fileList.length;i++){
upload_size_total+=fileRef.fileList[i].size;
}
upLoadImg(uploadImg_num);
delete_btn.enabled = false;
add_btn.enabled = false;
browse_btn.enabled = false;
}
//===================
// 执行删除操作
//===================
internal function deleteHandler(e:Event):void{
if(process_list.selectedIndices.length<=0){
Alert.show("您还没有选择图片","提示信息");
return;
}
Alert.show("您确定要删除选定图片?","提示信息",Alert.YES|Alert.NO,process_list,alertHandler,null,Alert.NO);
function alertHandler(evt:CloseEvent):void{
if(evt.detail==Alert.YES){
deleteItem();
} else {
}
}
function deleteItem():void{
var selectItems:Array = process_list.selectedItems;
var selectIndex:Array = process_list.selectedIndices;
var iCount:int = selectItems.length;
var sizeMum:Number = 0;
for(var i:int=0;i<iCount;i++){
info.splice(selectIndex[i],1);
fileRef.fileList.splice(selectIndex[i],1);
}
for(var j:Number=0;j<fileRef.fileList.length;j++){
sizeMum+=fileRef.fileList[j].size;
}
process_list.dataProvider = info;
tip_txt.text="共"+fileRef.fileList.length+"张图片,"+(sizeMum/(1000*1000)).toString().slice(0,-4)+"MB";
if(info.length<=0){
delete_btn.enabled = false;
}
}
}
//===================
// 执行添加操作
//===================
internal function addHandler(e:Event):void{
var imageTypes:FileFilter = new FileFilter("Images(*.jpg,*.jpeg,*.gif,*.png)","*.jpg;*.jpeg;*.gif;*.png");
var allTypes:Array = new Array(imageTypes);
var addFileRef:FileReferenceList = new FileReferenceList();
var sameImgIndex:Array = new Array();
var beforeAddLength:Number = fileRef.fileList.length;
addFileRef.browse(allTypes);
addFileRef.addEventListener(Event.SELECT,addSelectHandler);
function addSelectHandler():void{
if(addFileRef.fileList.length+fileRef.fileList.length>upload_maxCount){
Alert.show("一次最多只能上传"+upload_maxCount+"张图片","提示信息");
return;
}
for(var i:int=0;i<addFileRef.fileList.length;i++){
for(var j:int=0;j<fileRef.fileList.length;j++){
if(fileRef.fileList[j].name==addFileRef.fileList[i].name){
sameImgIndex.push(i);
}
}
}
for(var k:Number=0;k<addFileRef.fileList.length;k++){
fileRef.fileList.push(addFileRef.fileList[k]);
info.push({label:addFileRef.fileList[k].name,data:addFileRef.fileList[k].size/1000+"KB",width:0});
}
for(var m:Number=0;m<sameImgIndex.length;m++){
fileRef.fileList.splice(beforeAddLength+sameImgIndex[m]-m,1);
info.splice(beforeAddLength+sameImgIndex[m]-m,1);
}
var imageNum:int = fileRef.fileList.length;
var sizeMum:Number = 0;
for(var l:Number=0;l<imageNum;l++){
sizeMum+=fileRef.fileList[l].size;
}
process_list.dataProvider = info;
tip_txt.text="共"+imageNum+"张图片,"+(sizeMum/(1000*1000)).toString().slice(0,-4)+"MB";
delete_btn.enabled = true;
}
}
//===================
// 初始化函数
//===================
internal function initApp():void{
browse_btn.addEventListener(MouseEvent.CLICK,browseHandler);
fileRef.addEventListener(Event.SELECT,selectHandler);
delete_btn.addEventListener(MouseEvent.CLICK,deleteHandler);
add_btn.addEventListener(MouseEvent.CLICK,addHandler);
/* 注册供js调用的函数 ********************/
ExternalInterface.addCallback("uploadImg",uploadHandler);
}
]]>
</mx:Script>
<mx:Canvas>
<mx:Button label="浏览文件" id="browse_btn"/>
<mx:Label text="按住 Ctrl 或 Shift 键可以多选" x="85" y="3"/>
<mx:Button label="添加文件" x="330" id="add_btn" enabled="false"/>
<mx:Button label="删除文件" x="410" id="delete_btn" enabled="false"/>
<mx:Canvas width="490" height="25" backgroundColor="0Xf1f1f1" x="0" y="43" borderStyle="solid" borderColor="0Xbbbbbb">
<mx:Label text="文件名" x="5" y="3"/>
<mx:Label text="文件大小" x="215" y="3"/>
<mx:Label text="上传进度" x="320" y="3"/>
</mx:Canvas>
<mx:List x="0" y="67" width="490" rowCount="10" allowMultipleSelection="true" borderStyle="solid" borderColor="0Xbbbbbb" rowHeight="25" itemRenderer="ImageItem" id="process_list"/>
<mx:Canvas width="490" height="25" backgroundColor="0Xf1f1f1" x="0" y="318" borderStyle="solid" borderColor="0Xbbbbbb">
<mx:Label text="" fontWeight="bold" id="tip_txt" x="5" y="3"/>
</mx:Canvas>
<mx:Canvas borderStyle="solid" x="0" width="490" y="350" height="25" borderColor="0X124fc0" backgroundColor="0xffffff">
<mx:Canvas backgroundColor="0X124fc0" backgroundAlpha="0.5" id="processBar_Total" width="0" height="23"/>
</mx:Canvas>
</mx:Canvas>
</mx:Application>
这个是ImageItem.mxml:
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" height="25" borderSides="bottom" borderStyle="solid">
<mx:Label width="195" text="{data.label}" x="5"/>
<mx:Label width="100" text="{data.data}" x="210"/>
<mx:Canvas width="145" borderStyle="solid" borderColor="0X124fc0" height="7" x="320" y="5">
<mx:Canvas width="{data.width}" id="processBar" height="7" backgroundColor="0X124fc0" backgroundAlpha="0.5"/>
</mx:Canvas>
</mx:Canvas>
然后是html:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>无标题文档</title>
<style type="text/css">
*{margin:0px;padding:0px;}
#upload_flash{width:495px;height:380px;}
</style>
<script type="text/javascript">
//上传完成后的回调函数
function uploadCompelete(){
/location.href=http://www.baidu.com;
}
function submitForm(){
thisMovie("upload").uploadImg();
}
function thisMovie(movieName) {
if (navigator.appName.indexOf("Microsoft") != -1) {
return window[movieName];
} else {
return document[movieName];
}
}
</script>
</head>
<body>
<div id="upload_flash">
<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
id="upload" width="100%" height="100%"
codebase="http://fpdownload.macromedia.com/get/flashplayer/current/swflash.cab">
<param name="wmode" value="transparent">
<param name="movie" value="upload.swf" />
<param name="quality" value="high" />
<param name="bgcolor" value="#869ca7" />
<param name="allowScriptAccess" value="sameDomain" />
<embed src="upload.swf" quality="high" bgcolor="#869ca7" wmode="transparent"
width="100%" height="100%" name="upload" align="middle"
play="true"
loop="false"
quality="high"
allowScriptAccess="sameDomain"
type="application/x-shockwave-flash"
pluginspage="http://www.adobe.com/go/getflashplayer">
</embed>
</object>
</div>
<p><input type="button" value="提交" onclick="submitForm()" /></p>
</body>
</html>
最后是服务器端,服务器端很简单拉。我就放个php的吧:
<?php
//create the directory if doesn't exists (should have write permissons)
if(!is_dir("./files")) mkdir("./files", 0755);
//move the uploaded file
move_uploaded_file($_FILES['Filedata']['tmp_name'], "./files/".$_FILES['Filedata']['name']);
echo "aaa";
?>
===========================================================================
哈哈,效果还是蛮不错的。这里要说几个细节问题。很重要的心得:
1) as3的传值问题。 as3对事件监听只能使用函数名,不能传值,这是个很郁闷的地方。在网上搜过解决方案。大概有两种思路。一种是自定义一个类,扩展event,然后使用自己定义的类来监听事件。这个方法呢,我找了半天也没找到个具体的代码,很不好用,烦。另一种呢,是对触发事件的对象添加一个自己定义的属性,捆在对象上。然后呢,event.target来取得这个对象,再访问这个对象的这个属性。这种方法在写js的时候,我经常用,是很好用的方法。对象就当做是个容器,容器绑定我需要的变量,哈哈,超好用。只是,as3好像对对象扩展属性有限制,不能像js那样随心所欲地添加,需要先扩展一下类,然后才能对相应的对象添加属性。可是,超超超奇怪的是,这样一来,所有对象的这个属性居然指向同一地址!!!天哪,难道实例化对象之后,不是每个对象的属性都应该分配一个单纯的地址吗??太匪夷所思了。最后,这种方法也没给我帮上忙。只能利用全局的变量来实现参数传递了。可是,这哪里算传参呢?好在我这个功能不算复杂,如果复杂一点的功能,恐怕就惨了。
2)list组件的itemRenderer的访问。 在flash里,我们如果要访问父级元素的子级元素很简单,只需要parent_mc.child_mc.child_mc就可以访问到任意层级的子元素。可是flex里的机制不一样。比如说这个itemRenderer应该如何访问。我首先想到,list组件会不会用一个item数组来表示它里面的元素呢?比如,如果访问第2个元素里的adang_mc,我就用list.item[1].adang_mc来访问它。结果flexbuilder里压根就没有相应的属性提示。后来在一英文网站看到有人提了相同的问题,才知道原来是利用.indexToItemRenderer(num).document来访问子元素的。
3)list组件itemRenderer的渲染问题。 本以为只要用循环去一个一个访问就可以访问到所有itemRenderer里面的元素了。结果发现根本不是这么回事,只有可视范围内的itemRenderer才会被渲染,不对,更确切地说,只有可视范围内的itemRenderer才能被访问!!! 也就是说,如果你的list有二十个元素,而可视范围只有十个,当你访问第十一个的时候,它返回值为空!当前把滚动条拉下来时,那些本不可见的元素在可见范围的话,就可以访问了,而且本可见,现在被滚进去的元素现在又不能访问了。my god!!!这样编程多麻烦啊!我猜可能当初flex在设计list组件的时候,想尽量减少内存的开销吧,可是,这也太麻烦了吧??有没有解决办法呢?有。我想到的是,自动进行滚动,在flex中有相应的代码,那就是.scrollToIndex(index)。只是这个方法并会在index出现在可视范围外的时候才会调用,这个也是没想到的,不过并不影响我们的功能。