两个Android选择文件对话框
这个项目以及代码中使用的未在下面代码给出源码的方法都在这里:https://github.com/NashLegend/LegendUtils
第二种对话框的源码在这里:https://github.com/NashLegend/LegendExplorer/,这是一个文件浏览器源码。
Android文件对话框,一般用的分两种。
一是我们自己程序内使用的,当我们需要让用户选择一个文件或文件夹进行上传、下载或者其他操作时有时会用到。
二是系统的全局文件对话框,当一个程序发起一个要选择的Intent,那么这个对话框就会弹出,用户进行操作后返回行距的文件或者文件夹,比如写一封邮件如果想同时发送一个附件的时候,就会发起一个Intent,然后我们的选择文件对话框会弹出,让用户来选择文件。
这个文件对话框大约长下面这个样子(图标是不是很熟悉,这是直接取的ES文件浏览器的图标),它可以实现文件、文件夹的单选、多选、混选,当用户点击确定时,返回一个ArrayList<File>:
下面是如何写这个文件对话框。
首先我们需要一个,一个Dialog需要一个view来显示,也就是我们看到的。我们给它起名FileDialogView。很显然它需要两个Button用于确定取消,一个ImageButton用于返回上级,多选的话还要再加一个【全部选择、全部取消】的CheckBox(上图为单选文件夹的示例,所以没有出现).一个EditText用于显示当前路径、以及最重要的——ListView以及它的adapter,我们叫这个adapter为FileListAdapter。
下面是这个FileDialogView的代码(这个项目不难,代码里面的注释应该足够清楚了……)。
/***FileDialog的view**@authorNashLegend*/publicclassFileDialogViewextendsFrameLayoutimplementsOnClickListener,OnCheckedChangeListener{privateFileListAdapteradapter;privateListViewlistView;//文件列表privateEditTextpathText;//当前路径privateImageButtonbackButton;//返回上级按钮privateCheckBoxselectAllButton;//全选按钮privateintfileMode=FileDialog.FILE_MODE_OPEN_MULTI;//选择文件方式,默认为文件、文件夹混选privateStringinitialPath="/";//用来指定刚打开时的目录,默认为根目录privateButtoncancelButton;privateButtonokButton;publicFileDialogView(Contextcontext){super(context);initView(context);}publicFileDialogView(Contextcontext,AttributeSetattrs){super(context,attrs);initView(context);}publicFileDialogView(Contextcontext,AttributeSetattrs,intdefStyle){super(context,attrs,defStyle);initView(context);}/***初始化view*/privatevoidinitView(Contextcontext){LayoutInflaterinflater=(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);inflater.inflate(R.layout.dialog_file,this);listView=(ListView)findViewById(R.id.listview_dialog_file);pathText=(EditText)findViewById(R.id.edittext_dialog_file_path);backButton=(ImageButton)findViewById(R.id.p_w_picpathbutton_dialog_file_back);selectAllButton=(CheckBox)findViewById(R.id.checkbox_dialog_file_all);cancelButton=(Button)findViewById(R.id.button_dialog_file_cancel);okButton=(Button)findViewById(R.id.button_dialog_file_ok);backButton.setOnClickListener(this);cancelButton.setOnClickListener(this);okButton.setOnClickListener(this);selectAllButton.setOnCheckedChangeListener(this);pathText.setKeyListener(null);//不需要弹起键盘adapter=newFileListAdapter(context);adapter.setDialogView(this);listView.setAdapter(adapter);}/***打开目录**@paramfile要打开的文件夹**/publicvoidopenFolder(Filefile){if(!file.exists()||!file.isDirectory()){//若不存在此目录,则打开SD卡根目录file=Environment.getExternalStorageDirectory();}//openFolder用来读取文件列表详见FileListAdapter的代码adapter.openFolder(file);}/***打开目录**@parampath*要打开的文件夹路径*/publicvoidopenFolder(Stringpath){openFolder(newFile(path));}/***打开初始目录*/publicvoidopenFolder(){openFolder(initialPath);}/***返回上级目录*/privatevoidback2ParentLevel(){Filefile=adapter.getCurrentDirectory();//如果当前目录不为空且父目录不为空,则打开父目录if(file!=null&&file.getParentFile()!=null){openFolder(file.getParentFile());}}/***选中当前目录所有文件*/privatevoidselectAll(){adapter.selectAll();}/***取消选中当前目录所有文件*/privatevoidunselectAll(){adapter.unselectAll();}publicvoidunselectCheckBox(){selectAllButton.setOnCheckedChangeListener(null);selectAllButton.setChecked(false);selectAllButton.setOnCheckedChangeListener(this);}/***@return返回选中的文件列表*/publicArrayList<File>getSelectedFiles(){ArrayList<File>list=newArrayList<File>();if(adapter.getSelectedFiles().size()>0){list=adapter.getSelectedFiles();}else{//如果点击确定的时候没有选择文件并且模式是选择单个文件夹,那么就返回当前目录if(fileMode==FileDialog.FILE_MODE_OPEN_FOLDER_SINGLE){list.add(adapter.getCurrentDirectory());}}returnlist;}@OverridepublicvoidonClick(Viewv){intid=v.getId();if(id==R.id.p_w_picpathbutton_dialog_file_back){back2ParentLevel();}}publicEditTextgetPathText(){returnpathText;}publicintgetFileMode(){returnfileMode;}publicvoidsetFileMode(intfileMode){this.fileMode=fileMode;if(fileMode>FileDialog.FILE_MODE_OPEN_FILE_MULTI){//单选模式应该看不到全选按钮才对selectAllButton.setVisibility(View.GONE);}else{selectAllButton.setVisibility(View.VISIBLE);}}publicStringgetInitialPath(){returninitialPath;}publicvoidsetInitialPath(StringinitialPath){this.initialPath=initialPath;}@OverridepublicvoidonCheckedChanged(CompoundButtonbuttonView,booleanisChecked){if(selectAllButton.isChecked()){selectAll();}else{unselectAll();}}publicCheckBoxgetSelectAllButton(){returnselectAllButton;}}
FileDialogView代码并不多,只负责了构建界面的任务。
下面是FileListAdapter的代码,FileListAdapter负责读取文件夹、全选、反选、排序、返回选中文件,数据对象为FileItem:
packagecom.example.legendutils.BuildIn;importjava.io.File;importjava.util.ArrayList;importjava.util.Collections;importjava.util.Comparator;importjava.util.Iterator;importcom.example.legendutils.Dialogs.FileDialog;importandroid.annotation.SuppressLint;importandroid.content.Context;importandroid.text.TextUtils;importandroid.util.Log;importandroid.view.View;importandroid.view.ViewGroup;importandroid.widget.BaseAdapter;publicclassFileListAdapterextendsBaseAdapter{privateArrayList<FileItem>list=newArrayList<FileItem>();privateContextmContext;privateFilecurrentDirectory;privateFileDialogViewdialogView;publicFileListAdapter(ContextContext){mContext=Context;}@OverridepublicintgetCount(){returnlist.size();}@OverridepublicObjectgetItem(intposition){returnlist.get(position);}@OverridepubliclonggetItemId(intposition){returnposition;}@OverridepublicViewgetView(intposition,ViewconvertView,ViewGroupparent){ViewHolderholder=null;if(convertView==null){holder=newViewHolder();convertView=newFileItemView(mContext);holder.fileItemView=(FileItemView)convertView;convertView.setTag(holder);}else{holder=(ViewHolder)convertView.getTag();}holder.fileItemView.setFileItem(list.get(position),this,dialogView.getFileMode());returnholder.fileItemView;}classViewHolder{FileItemViewfileItemView;}publicArrayList<FileItem>getList(){returnlist;}publicvoidsetList(ArrayList<FileItem>list){this.list=list;}/***打开文件夹,更新文件列表**@paramfile*/publicvoidopenFolder(Filefile){if(file!=null&&file.exists()&&file.isDirectory()){if(!file.equals(currentDirectory)){//与当前目录不同currentDirectory=file;list.clear();File[]files=file.listFiles();if(files!=null){for(inti=0;i<files.length;i++){FiletmpFile=files[i];if(tmpFile.isFile()&&(dialogView.getFileMode()==FileDialog.FILE_MODE_OPEN_FOLDER_MULTI||dialogView.getFileMode()==FileDialog.FILE_MODE_OPEN_FOLDER_SINGLE)){//如果只能选择文件夹并且当前文件不是文件夹,那则跳过continue;}list.add(newFileItem(files[i]));}}files=null;sortList();notifyDataSetChanged();}}//改变FileDialogView的当前路径显示dialogView.getPathText().setText(file.getAbsolutePath());}/***选择当前目录下所有文件*/publicvoidselectAll(){intmode=dialogView.getFileMode();if(mode>FileDialog.FILE_MODE_OPEN_FILE_MULTI){//这个if不会发生,我为啥要写……return;}for(Iterator<FileItem>iterator=list.iterator();iterator.hasNext();){FileItemfileItem=(FileItem)iterator.next();if(mode==FileDialog.FILE_MODE_OPEN_FILE_MULTI&&fileItem.isDirectory()){//fileItem是目录,但是只能选择文件,则跳过continue;}if(mode==FileDialog.FILE_MODE_OPEN_FOLDER_MULTI&&!fileItem.isDirectory()){//fileItem是文件,但是只能选择目录,则跳过continue;}fileItem.setSelected(true);}notifyDataSetChanged();}/***取消所有文件的选中状态*/publicvoidunselectAll(){for(Iterator<FileItem>iterator=list.iterator();iterator.hasNext();){FileItemfileItem=(FileItem)iterator.next();fileItem.setSelected(false);}notifyDataSetChanged();}/***选中一个文件,只在选中时调用,取消选中不调用,且只由FileItemView调用**@paramfileItem*/publicvoidselectOne(FileItemfileItem){intmode=dialogView.getFileMode();if(mode>FileDialog.FILE_MODE_OPEN_FILE_MULTI){//如果是单选if(mode==FileDialog.FILE_MODE_OPEN_FILE_SINGLE&&fileItem.isDirectory()){//fileItem是目录,但是只能选择文件,则返回return;}if(mode==FileDialog.FILE_MODE_OPEN_FOLDER_SINGLE&&!fileItem.isDirectory()){//fileItem是文件,但是只能选择目录,则返回return;}for(Iterator<FileItem>iterator=list.iterator();iterator.hasNext();){FileItemtmpItem=(FileItem)iterator.next();if(tmpItem.equals(fileItem)){tmpItem.setSelected(true);}else{tmpItem.setSelected(false);}}}else{//如果是多选if(mode==FileDialog.FILE_MODE_OPEN_FILE_MULTI&&fileItem.isDirectory()){//fileItem是目录,但是只能选择文件,则返回return;}if(mode==FileDialog.FILE_MODE_OPEN_FOLDER_MULTI&&!fileItem.isDirectory()){//fileItem是文件,但是只能选择目录,则返回return;}fileItem.setSelected(true);}notifyDataSetChanged();}publicvoidsortList(){FileItemComparatorcomparator=newFileItemComparator();Collections.sort(list,comparator);}/***取消一个的选择,其他逻辑都在FileItemView里面*/publicvoidunselectOne(){dialogView.unselectCheckBox();}/***@return选中的文件列表*/publicArrayList<File>getSelectedFiles(){ArrayList<File>selectedFiles=newArrayList<File>();for(Iterator<FileItem>iterator=list.iterator();iterator.hasNext();){FileItemfile=iterator.next();//强制转换为Fileif(file.isSelected()){selectedFiles.add(file);}}returnselectedFiles;}publicclassFileItemComparatorimplementsComparator<FileItem>{@Overridepublicintcompare(FileItemlhs,FileItemrhs){if(lhs.isDirectory()!=rhs.isDirectory()){//如果一个是文件,一个是文件夹,优先按照类型排序if(lhs.isDirectory()){return-1;}else{return1;}}else{//如果同是文件夹或者文件,则按名称排序returnlhs.getName().toLowerCase().compareTo(rhs.getName().toLowerCase());}}}publicFilegetCurrentDirectory(){returncurrentDirectory;}publicFileDialogViewgetDialogView(){returndialogView;}publicvoidsetDialogView(FileDialogViewdialogView){this.dialogView=dialogView;}}
下面是FileItemView,它是ListView的元素,用来显示每一个文件。数据对象为FileItem
/***文件列表单个item的view**@authorNashLegend*/publicclassFileItemViewextendsFrameLayoutimplementsOnClickListener,OnCheckedChangeListener{privateImageViewicon;//文件图标privateTextViewtitle;//文件名privateCheckBoxcheckBox;//选择按钮privateViewGrouprootFileItemView;//FileItemView的xml文件的根viewprivateFileListAdapteradapter;privateintfileMode=FileDialog.FILE_MODE_OPEN_MULTI;privatebooleanselectable=true;privateFileItemfileItem;publicFileItemView(Contextcontext){super(context);LayoutInflaterinflater=(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);inflater.inflate(R.layout.view_file_item,this);icon=(ImageView)findViewById(R.id.p_w_picpath_file_icon);title=(TextView)findViewById(R.id.text_file_title);rootFileItemView=(ViewGroup)findViewById(R.id.rootFileItemView);checkBox=(CheckBox)findViewById(R.id.checkbox_file_item_select);setOnClickListener(this);}publicFileItemgetFileItem(){returnfileItem;}publicvoidsetFileItem(FileItemfileItem,FileListAdapteradapter,intfileMode){this.fileItem=fileItem;this.adapter=adapter;this.fileMode=fileMode;icon.setImageResource(fileItem.getIcon());title.setText(fileItem.getName());toggleSelectState();if(!fileItem.isDirectory()&&(fileMode==FileDialog.FILE_MODE_OPEN_FOLDER_MULTI||fileMode==FileDialog.FILE_MODE_OPEN_FOLDER_SINGLE)){//如果选择模式与当前文件类型不符,则设计为不可选择,比如在只可选择文件平时,文件不可选checkBox.setEnabled(false);selectable=false;checkBox.setOnCheckedChangeListener(null);return;}if(fileItem.isDirectory()&&(fileMode==FileDialog.FILE_MODE_OPEN_FILE_MULTI||fileMode==FileDialog.FILE_MODE_OPEN_FILE_SINGLE)){//如果选择模式与当前文件类型不符,则设计为不可选择,比如在只可选择文件时,文件夹不可选checkBox.setEnabled(false);selectable=false;checkBox.setOnCheckedChangeListener(null);return;}selectable=true;checkBox.setEnabled(true);checkBox.setOnCheckedChangeListener(this);}/***切换选中、未选中状态,fileItem.setSelected(boolean)先发生;*/publicvoidtoggleSelectState(){if(fileItem.isSelected()){rootFileItemView.setBackgroundResource(R.drawable.bg_file_item_select);}else{rootFileItemView.setBackgroundResource(R.drawable.bg_file_item_normal);}checkBox.setOnCheckedChangeListener(null);checkBox.setChecked(fileItem.isSelected());checkBox.setOnCheckedChangeListener(this);}@OverridepublicvoidonClick(Viewv){if(v.getId()!=R.id.checkbox_file_item_select){//被点击时,如果是文件夹则打开文件夹,如果是文件则选中文件if(fileItem.isDirectory()){openFolder();}else{//选中一个selectOne();}}}publicvoidselectOne(){//选中一个文件(夹)if(selectable){if(fileItem.isSelected()){//取消选中状态,只在FileItemView就可以fileItem.setSelected(!fileItem.isSelected());toggleSelectState();adapter.unselectOne();}else{//如果要选中某个FileItem,则必须要在adapter里面进行,因为如果是单选的话,还要取消其他的选中状态adapter.selectOne(fileItem);}}}/***打开文件夹*/publicvoidopenFolder(){adapter.openFolder(fileItem);}publicFileListAdaptergetAdapter(){returnadapter;}@OverridepublicvoidonCheckedChanged(CompoundButtonbuttonView,booleanisChecked){if(isChecked){adapter.selectOne(fileItem);}else{fileItem.setSelected(false);rootFileItemView.setBackgroundResource(R.drawable.bg_file_item_normal);adapter.unselectOne();}}publicintgetFileMode(){returnfileMode;}}
上面所使用的数据对象FileItem其实很简单,只是一个继承了File,并仅仅多了icon字段和selected字段的类。这里不写出来了,详见上面的地址。
现在有了View,只要把它放到Dialog里就可以了,Dialog很简单了,我们仍然依照系统的Dialog写一个Builder以方便使用。代码如下:
publicclassFileDialogextendsDialog{/***以打开文件模式打开文件对话框,有可能是文件夹也有可能是文件,可多选,最终返回值为一个File对象列表。*/publicstaticfinalintFILE_MODE_OPEN_MULTI=0;/***以打开文件模式打开文件对话框,只能选择文件夹而不是文件,可多选,最终返回值为一个File对象列表。*/publicstaticfinalintFILE_MODE_OPEN_FOLDER_MULTI=1;/***以打开文件模式打开文件对话框,只能选择文件而不是文件夹,可多选,最终返回值为一个File对象列表。*/publicstaticfinalintFILE_MODE_OPEN_FILE_MULTI=2;/***以打开文件模式打开文件对话框,有可能是文件夹也有可能是文件,最终返回值为一个长度为1的File对象列表。*/publicstaticfinalintFILE_MODE_OPEN_SINGLE=3;/***以打开文件模式打开文件对话框,只能选择文件夹而不是文件,最终返回值为一个长度为1的File对象列表。*/publicstaticfinalintFILE_MODE_OPEN_FOLDER_SINGLE=4;/***以打开文件模式打开文件对话框,只能选择文件而不是文件夹,最终返回值为一个长度为1的File对象列表。*/publicstaticfinalintFILE_MODE_OPEN_FILE_SINGLE=5;publicFileDialog(Contextcontext){super(context);}publicFileDialog(Contextcontext,inttheme){super(context,theme);}publicFileDialog(Contextcontext,booleancancelable,OnCancelListenercancelListener){super(context,cancelable,cancelListener);}publicinterfaceFileDialogListener{publicvoidonFileSelected(ArrayList<File>files);publicvoidonFileCanceled();}publicstaticclassBuilder{privateintfileMode=FileDialog.FILE_MODE_OPEN_MULTI;privateStringinitialPath=Environment.getExternalStorageDirectory().getAbsolutePath();privateFileDialogListenerfileSelectListener;privateFileDialogViewdialogView;privateContextcontext;privatebooleancanceledOnTouchOutside=true;privatebooleancancelable=true;privateStringtitle="选择文件";publicBuilder(Contextcontext){this.context=context;}publicBuildersetCanceledOnTouchOutside(booleanflag){canceledOnTouchOutside=flag;returnthis;}publicBuildersetCancelable(booleanflag){cancelable=flag;returnthis;}publicBuildersetFileMode(intfileMode){this.fileMode=fileMode;returnthis;}publicBuildersetInitialPath(StringinitialPath){this.initialPath=initialPath;returnthis;}publicBuildersetTitle(Stringtitle){this.title=title;returnthis;}publicBuildersetFileSelectListener(FileDialogListenerfileSelectListener){this.fileSelectListener=fileSelectListener;returnthis;}/***必须强制设置dialog的大小,因为ListView大小必须确定,否则ListView的Adapter的getView会执行很多遍,*次数取决于listview最终能显示多少项。**@return*/publicFileDialogcreate(intwidth,intheight){finalFileDialogdialog=newFileDialog(context);dialogView=newFileDialogView(context);dialogView.setFileMode(fileMode);dialogView.setInitialPath(initialPath);dialogView.openFolder();dialog.setTitle(title);dialog.setCancelable(cancelable);dialog.setCanceledOnTouchOutside(canceledOnTouchOutside);dialog.setContentView(dialogView,newLayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT));if(width>0&&height>0){dialog.getWindow().setLayout(width,height);}ButtonokButton=(Button)dialogView.findViewById(R.id.button_dialog_file_ok);ButtoncancelButton=(Button)dialogView.findViewById(R.id.button_dialog_file_cancel);okButton.setOnClickListener(newView.OnClickListener(){@OverridepublicvoidonClick(Viewv){//点击确定按钮,返回文件列表if(fileSelectListener!=null){if(dialogView.getSelectedFiles().size()>0){fileSelectListener.onFileSelected(dialogView.getSelectedFiles());}else{fileSelectListener.onFileCanceled();}}dialog.dismiss();}});cancelButton.setOnClickListener(newView.OnClickListener(){@OverridepublicvoidonClick(Viewv){//点击取消按钮,直接dismissif(fileSelectListener!=null){fileSelectListener.onFileCanceled();}dialog.dismiss();}});returndialog;}/***使得FileDialog大小和activity一样,在Activity创建完成之前,返回的数字可能不对**@paramactivity*@return*/publicFileDialogcreate(Activityactivity){//下面这两个方法是获得窗口的宽高,方法不在这里贴出了,详情见上面给出的项目地址intwidth=DisplayUtil.getWindowWidth(activity);intheight=DisplayUtil.getWindowHeight(activity);returncreate(width,height);}}}
如何使用它:
FileDialogdialog=newFileDialog.Builder(getActivity()).setFileMode(FileDialog.FILE_MODE_OPEN_FOLDER_SINGLE).setCancelable(true).setCanceledOnTouchOutside(false).setTitle("selectFolder").setFileSelectListener(newFileDialogListener(){@OverridepublicvoidonFileSelected(ArrayList<File>files){if(files.size()>0){copy2Folder(getSelectedFiles(),files.get(0));}}@OverridepublicvoidonFileCanceled(){ToastUtil.showToast(getActivity(),"CopyCancelled!");}}).create(getActivity());dialog.show();
至于第二种接收系统通知其实在同小异,核心代码都跟上面一样,唯一的区别是,它其实是一个Activity,我们叫它PickerActivity,使用了FileDialogView的Activity,而上面的是Dialog……
要接收打开文件的Intent,要在AndroidMenifest.xml的这个Activity节点***册IntentFilter。如下:
<intent-filter><actionandroid:name="android.intent.action.GET_CONTENT"/><categoryandroid:name="android.intent.category.OPENABLE"/><categoryandroid:name="android.intent.category.DEFAULT"/><dataandroid:mimeType="*/*"/></intent-filter>
PickerActivity代码,跟FileDialog基本差不多。
publicclassPickerActivityextendsActivity{privateFileDialogViewpickerView;privateButtoncancelButton;privateButtonokButton;@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_picker);setTitle("PickAFile");Intentintent=getIntent();if(intent!=null&&Intent.ACTION_GET_CONTENT.equals(intent.getAction())){pickerView=(FileDialogView)findViewById(R.id.picker);pickerView.setFileMode(FileDialog.FILE_MODE_OPEN_FILE_SINGLE);pickerView.setInitialPath(Environment.getExternalStorageDirectory().getAbsolutePath());pickerView.openFolder();cancelButton=(Button)pickerView.findViewById(com.example.legendutils.R.id.button_dialog_file_cancel);okButton=(Button)pickerView.findViewById(com.example.legendutils.R.id.button_dialog_file_ok);cancelButton.setOnClickListener(newOnClickListener(){@OverridepublicvoidonClick(Viewv){setResult(RESULT_CANCELED);finish();}});okButton.setOnClickListener(newOnClickListener(){@OverridepublicvoidonClick(Viewv){ArrayList<File>files=pickerView.getSelectedFiles();if(files!=null&&files.size()>0){Filefile=files.get(0);Intentintent=newIntent();Uriuri=Uri.fromFile(file);intent.setData(uri);setResult(RESULT_OK,intent);finish();}}});}}}
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。