这个项目以及代码中使用的未在下面代码给出源码的方法都在这里:https://github.com/NashLegend/LegendUtils


第二种对话框的源码在这里:https://github.com/NashLegend/LegendExplorer/,这是一个文件浏览器源码。




Android文件对话框,一般用的分两种。

一是我们自己程序内使用的,当我们需要让用户选择一个文件或文件夹进行上传、下载或者其他操作时有时会用到。

二是系统的全局文件对话框,当一个程序发起一个要选择的Intent,那么这个对话框就会弹出,用户进行操作后返回行距的文件或者文件夹,比如写一封邮件如果想同时发送一个附件的时候,就会发起一个Intent,然后我们的选择文件对话框会弹出,让用户来选择文件。


这个文件对话框大约长下面这个样子(图标是不是很熟悉,这是直接取的ES文件浏览器的图标),它可以实现文件、文件夹的单选、多选、混选,当用户点击确定时,返回一个ArrayList


下面是如何写这个文件对话框。


首先我们需要一个,一个Dialog需要一个view来显示,也就是我们看到的。我们给它起名FileDialogView。很显然它需要两个Button用于确定取消,一个ImageButton用于返回上级,多选的话还要再加一个【全部选择、全部取消】的CheckBox(上图为单选文件夹的示例,所以没有出现).一个EditText用于显示当前路径、以及最重要的——ListView以及它的adapter,我们叫这个adapter为FileListAdapter。

下面是这个FileDialogView的代码(这个项目不难,代码里面的注释应该足够清楚了……)。

/** * FileDialog的view *  * @author NashLegend */public class FileDialogView extends FrameLayout implements OnClickListener,OnCheckedChangeListener {private FileListAdapter adapter;private ListView listView;//文件列表private EditText pathText;//当前路径private ImageButton backButton;//返回上级按钮private CheckBox selectAllButton;//全选按钮private int fileMode = FileDialog.FILE_MODE_OPEN_MULTI;//选择文件方式,默认为文件、文件夹混选private String initialPath = "/";//用来指定刚打开时的目录,默认为根目录private Button cancelButton;private Button okButton;public FileDialogView(Context context) {super(context);initView(context);}public FileDialogView(Context context, AttributeSet attrs) {super(context, attrs);initView(context);}public FileDialogView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);initView(context);}/**         * 初始化view         */private void initView(Context context) {LayoutInflater inflater = (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 = new FileListAdapter(context);adapter.setDialogView(this);listView.setAdapter(adapter);}/** * 打开目录 *  * @param file 要打开的文件夹 *             */public void openFolder(File file) {if (!file.exists() || !file.isDirectory()) {// 若不存在此目录,则打开SD卡根目录file = Environment.getExternalStorageDirectory();}//openFolder用来读取文件列表详见FileListAdapter的代码adapter.openFolder(file);}/** * 打开目录 *  * @param path *            要打开的文件夹路径 */public void openFolder(String path) {openFolder(new File(path));}/** * 打开初始目录 */public void openFolder() {openFolder(initialPath);}/** * 返回上级目录 */private void back2ParentLevel() {File file = adapter.getCurrentDirectory();// 如果当前目录不为空且父目录不为空,则打开父目录if (file != null && file.getParentFile() != null) {openFolder(file.getParentFile());}}/** * 选中当前目录所有文件 */private void selectAll() {adapter.selectAll();}/** * 取消选中当前目录所有文件 */private void unselectAll() {adapter.unselectAll();}public void unselectCheckBox() {selectAllButton.setOnCheckedChangeListener(null);selectAllButton.setChecked(false);selectAllButton.setOnCheckedChangeListener(this);}/** * @return 返回选中的文件列表 */public ArrayList getSelectedFiles() {ArrayList list = new ArrayList();if (adapter.getSelectedFiles().size() > 0) {list = adapter.getSelectedFiles();} else {        //如果点击确定的时候没有选择文件并且模式是选择单个文件夹,那么就返回当前目录if (fileMode == FileDialog.FILE_MODE_OPEN_FOLDER_SINGLE) {list.add(adapter.getCurrentDirectory());}}return list;}@Overridepublic void onClick(View v) {int id = v.getId();if (id == R.id.p_w_picpathbutton_dialog_file_back) {back2ParentLevel();}}public EditText getPathText() {return pathText;}public int getFileMode() {return fileMode;}public void setFileMode(int fileMode) {this.fileMode = fileMode;if (fileMode > FileDialog.FILE_MODE_OPEN_FILE_MULTI) {// 单选模式应该看不到全选按钮才对selectAllButton.setVisibility(View.GONE);} else {selectAllButton.setVisibility(View.VISIBLE);}}public String getInitialPath() {return initialPath;}public void setInitialPath(String initialPath) {this.initialPath = initialPath;}@Overridepublic void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {if (selectAllButton.isChecked()) {selectAll();} else {unselectAll();}}public CheckBox getSelectAllButton() {return selectAllButton;}}

FileDialogView代码并不多,只负责了构建界面的任务。


下面是FileListAdapter的代码,FileListAdapter负责读取文件夹、全选、反选、排序、返回选中文件,数据对象为FileItem:

package com.example.legendutils.BuildIn;import java.io.File;import java.util.ArrayList;import java.util.Collections;import java.util.Comparator;import java.util.Iterator;import com.example.legendutils.Dialogs.FileDialog;import android.annotation.SuppressLint;import android.content.Context;import android.text.TextUtils;import android.util.Log;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;public class FileListAdapter extends BaseAdapter {    private ArrayList list = new ArrayList();    private Context mContext;    private File currentDirectory;    private FileDialogView dialogView;    public FileListAdapter(Context Context) {        mContext = Context;    }    @Override    public int getCount() {        return list.size();    }    @Override    public Object getItem(int position) {        return list.get(position);    }    @Override    public long getItemId(int position) {        return position;    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {        ViewHolder holder = null;        if (convertView == null) {            holder = new ViewHolder();            convertView = new FileItemView(mContext);            holder.fileItemView = (FileItemView) convertView;            convertView.setTag(holder);        } else {            holder = (ViewHolder) convertView.getTag();        }        holder.fileItemView.setFileItem(list.get(position), this,                dialogView.getFileMode());        return holder.fileItemView;    }    class ViewHolder {        FileItemView fileItemView;    }    public ArrayList getList() {        return list;    }    public void setList(ArrayList list) {        this.list = list;    }    /**     * 打开文件夹,更新文件列表     *      * @param file     */    public void openFolder(File file) {        if (file != null && file.exists() && file.isDirectory()) {            if (!file.equals(currentDirectory)) {                // 与当前目录不同                currentDirectory = file;                list.clear();                File[] files = file.listFiles();                if (files != null) {                    for (int i = 0; i < files.length; i++) {                        File tmpFile = files[i];if (tmpFile.isFile()&& (dialogView.getFileMode() == FileDialog.FILE_MODE_OPEN_FOLDER_MULTI || dialogView    .getFileMode() == FileDialog.FILE_MODE_OPEN_FOLDER_SINGLE)) {    //如果只能选择文件夹并且当前文件不是文件夹,那则跳过continue;}                                           list.add(new FileItem(files[i]));                    }                }                files = null;                sortList();                notifyDataSetChanged();            }        }        //改变FileDialogView的当前路径显示        dialogView.getPathText().setText(file.getAbsolutePath());    }    /**     * 选择当前目录下所有文件     */    public void selectAll() {        int mode = dialogView.getFileMode();        if (mode > FileDialog.FILE_MODE_OPEN_FILE_MULTI) {            // 这个if不会发生,我为啥要写……            return;        }        for (Iterator iterator = list.iterator(); iterator.hasNext();) {            FileItem fileItem = (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();    }    /**     * 取消所有文件的选中状态     */    public void unselectAll() {        for (Iterator iterator = list.iterator(); iterator.hasNext();) {            FileItem fileItem = (FileItem) iterator.next();            fileItem.setSelected(false);        }        notifyDataSetChanged();    }    /**     * 选中一个文件,只在选中时调用,取消选中不调用,且只由FileItemView调用     *      * @param fileItem     */    public void selectOne(FileItem fileItem) {        int mode = 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 iterator = list.iterator(); iterator                    .hasNext();) {                FileItem tmpItem = (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();    }    public void sortList() {        FileItemComparator comparator = new FileItemComparator();        Collections.sort(list, comparator);    }    /**     * 取消一个的选择,其他逻辑都在FileItemView里面     */    public void unselectOne() {        dialogView.unselectCheckBox();    }    /**     * @return 选中的文件列表     */    public ArrayList getSelectedFiles() {        ArrayList selectedFiles = new ArrayList();        for (Iterator iterator = list.iterator(); iterator.hasNext();) {            FileItem file = iterator.next();// 强制转换为File            if (file.isSelected()) {                selectedFiles.add(file);            }        }        return selectedFiles;    }    public class FileItemComparator implements Comparator {        @Override        public int compare(FileItem lhs, FileItem rhs) {            if (lhs.isDirectory() != rhs.isDirectory()) {                // 如果一个是文件,一个是文件夹,优先按照类型排序                if (lhs.isDirectory()) {                    return -1;                } else {                    return 1;                }            } else {                // 如果同是文件夹或者文件,则按名称排序                return lhs.getName().toLowerCase().compareTo(rhs.getName().toLowerCase());            }        }    }    public File getCurrentDirectory() {        return currentDirectory;    }    public FileDialogView getDialogView() {        return dialogView;    }    public void setDialogView(FileDialogView dialogView) {        this.dialogView = dialogView;    }}

下面是FileItemView,它是ListView的元素,用来显示每一个文件。数据对象为FileItem

/** * 文件列表单个item的view *  * @author NashLegend */public class FileItemView extends FrameLayout implements OnClickListener,OnCheckedChangeListener {private ImageView icon;//文件图标private TextView title;//文件名private CheckBox checkBox;//选择按钮private ViewGroup rootFileItemView;//FileItemView的xml文件的根viewprivate FileListAdapter adapter;private int fileMode = FileDialog.FILE_MODE_OPEN_MULTI;private boolean selectable = true;private FileItem fileItem;public FileItemView(Context context) {super(context);LayoutInflater inflater = (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);}public FileItem getFileItem() {return fileItem;}public void setFileItem(FileItem fileItem, FileListAdapter adapter,int fileMode) {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)先发生; */public void toggleSelectState() {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);}@Overridepublic void onClick(View v) {if (v.getId() != R.id.checkbox_file_item_select) {        //被点击时,如果是文件夹则打开文件夹,如果是文件则选中文件if (fileItem.isDirectory()) {openFolder();} else {// 选中一个selectOne();}}}public void selectOne() {//选中一个文件(夹)if (selectable) {if (fileItem.isSelected()) {// 取消选中状态,只在FileItemView就可以fileItem.setSelected(!fileItem.isSelected());toggleSelectState();adapter.unselectOne();} else {// 如果要选中某个FileItem,则必须要在adapter里面进行,因为如果是单选的话,还要取消其他的选中状态adapter.selectOne(fileItem);}}}/** * 打开文件夹 */public void openFolder() {adapter.openFolder(fileItem);}public FileListAdapter getAdapter() {return adapter;}@Overridepublic void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {if (isChecked) {adapter.selectOne(fileItem);} else {fileItem.setSelected(false);rootFileItemView.setBackgroundResource(R.drawable.bg_file_item_normal);adapter.unselectOne();}}public int getFileMode() {return fileMode;}}

上面所使用的数据对象FileItem其实很简单,只是一个继承了File,并仅仅多了icon字段和selected字段的类。这里不写出来了,详见上面的地址。


现在有了View,只要把它放到Dialog里就可以了,Dialog很简单了,我们仍然依照系统的Dialog写一个Builder以方便使用。代码如下:

public class FileDialog extends Dialog {/** * 以打开文件模式打开文件对话框,有可能是文件夹也有可能是文件,可多选,最终返回值为一个File对象列表。 */public static final int FILE_MODE_OPEN_MULTI = 0;/** * 以打开文件模式打开文件对话框,只能选择文件夹而不是文件,可多选,最终返回值为一个File对象列表。 */public static final int FILE_MODE_OPEN_FOLDER_MULTI = 1;/** * 以打开文件模式打开文件对话框,只能选择文件而不是文件夹,可多选,最终返回值为一个File对象列表。 */public static final int FILE_MODE_OPEN_FILE_MULTI = 2;/** * 以打开文件模式打开文件对话框,有可能是文件夹也有可能是文件,最终返回值为一个长度为1的File对象列表。 */public static final int FILE_MODE_OPEN_SINGLE = 3;/** * 以打开文件模式打开文件对话框,只能选择文件夹而不是文件,最终返回值为一个长度为1的File对象列表。 */public static final int FILE_MODE_OPEN_FOLDER_SINGLE = 4;/** * 以打开文件模式打开文件对话框,只能选择文件而不是文件夹,最终返回值为一个长度为1的File对象列表。 */public static final int FILE_MODE_OPEN_FILE_SINGLE = 5;public FileDialog(Context context) {super(context);}public FileDialog(Context context, int theme) {super(context, theme);}public FileDialog(Context context, boolean cancelable,OnCancelListener cancelListener) {super(context, cancelable, cancelListener);}public interface FileDialogListener {public void onFileSelected(ArrayList files);public void onFileCanceled();}public static class Builder {private int fileMode = FileDialog.FILE_MODE_OPEN_MULTI;private String initialPath = Environment.getExternalStorageDirectory().getAbsolutePath();private FileDialogListener fileSelectListener;private FileDialogView dialogView;private Context context;private boolean canceledOnTouchOutside = true;private boolean cancelable = true;private String title = "选择文件";public Builder(Context context) {this.context = context;}public Builder setCanceledOnTouchOutside(boolean flag) {canceledOnTouchOutside = flag;return this;}public Builder setCancelable(boolean flag) {cancelable = flag;return this;}public Builder setFileMode(int fileMode) {this.fileMode = fileMode;return this;}public Builder setInitialPath(String initialPath) {this.initialPath = initialPath;return this;}public Builder setTitle(String title) {this.title = title;return this;}public Builder setFileSelectListener(FileDialogListener fileSelectListener) {this.fileSelectListener = fileSelectListener;return this;}/** * 必须强制设置dialog的大小,因为ListView大小必须确定,否则ListView的Adapter的getView会执行很多遍, * 次数取决于listview最终能显示多少项。 *  * @return */public FileDialog create(int width, int height) {final FileDialog dialog = new FileDialog(context);dialogView = new FileDialogView(context);dialogView.setFileMode(fileMode);dialogView.setInitialPath(initialPath);dialogView.openFolder();dialog.setTitle(title);dialog.setCancelable(cancelable);dialog.setCanceledOnTouchOutside(canceledOnTouchOutside);dialog.setContentView(dialogView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));if (width > 0 && height > 0) {dialog.getWindow().setLayout(width, height);}Button okButton = (Button) dialogView.findViewById(R.id.button_dialog_file_ok);Button cancelButton = (Button) dialogView.findViewById(R.id.button_dialog_file_cancel);okButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 点击确定按钮,返回文件列表if (fileSelectListener != null) {if (dialogView.getSelectedFiles().size() > 0) {fileSelectListener.onFileSelected(dialogView.getSelectedFiles());} else {fileSelectListener.onFileCanceled();}}dialog.dismiss();}});cancelButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {        //点击取消按钮,直接dismissif (fileSelectListener != null) {fileSelectListener.onFileCanceled();}dialog.dismiss();}});return dialog;}/** * 使得FileDialog大小和activity一样,在Activity创建完成之前,返回的数字可能不对 *  * @param activity * @return */public FileDialog create(Activity activity) {        //下面这两个方法是获得窗口的宽高,方法不在这里贴出了,详情见上面给出的项目地址int width = DisplayUtil.getWindowWidth(activity);int height = DisplayUtil.getWindowHeight(activity);return create(width, height);}}}


如何使用它:


FileDialog dialog = new FileDialog.Builder(getActivity()).setFileMode(FileDialog.FILE_MODE_OPEN_FOLDER_SINGLE).setCancelable(true).setCanceledOnTouchOutside(false).setTitle("selectFolder").setFileSelectListener(new FileDialogListener() {@Overridepublic void onFileSelected(ArrayList files) {if (files.size() > 0) {copy2Folder(getSelectedFiles(), files.get(0));}}@Overridepublic void onFileCanceled() {ToastUtil.showToast(getActivity(), "Copy Cancelled!");}}).create(getActivity());dialog.show();





至于第二种接收系统通知其实在同小异,核心代码都跟上面一样,唯一的区别是,它其实是一个Activity,我们叫它PickerActivity,使用了FileDialogView的Activity,而上面的是Dialog……

要接收打开文件的Intent,要在AndroidMenifest.xml的这个Activity节点***册IntentFilter。如下:

                                                                                        

PickerActivity代码,跟FileDialog基本差不多。

public class PickerActivity extends Activity {private FileDialogView pickerView;private Button cancelButton;private Button okButton;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_picker);setTitle("Pick A File");Intent intent = 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(new OnClickListener() {@Overridepublic void onClick(View v) {setResult(RESULT_CANCELED);finish();}});okButton.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {ArrayList files = pickerView.getSelectedFiles();if (files != null && files.size() > 0) {File file = files.get(0);Intent intent = new Intent();Uri uri = Uri.fromFile(file);intent.setData(uri);setResult(RESULT_OK, intent);finish();}}});}}}


更多相关文章

  1. Android(安卓)Studio中Gradle的Daemon
  2. Tinker Android热补丁
  3. 【Android系统源码修改】如何用Android(安卓)MTK源码生成签名文
  4. android与PC,C#与Java 利用protobuf 进行无障碍通讯【Socket】
  5. android4.0.3 修改启动动画和开机声音
  6. Android桌面组件App Widget开发三步走
  7. android apk 自我保护技术-加密apk
  8. Android应用程序开发以及背后的设计思想深度剖析 (1)
  9. Android(安卓)SDK Manager无法显示可供下载的未安装SDK解决方案

随机推荐

  1. android后台进程的问题,大家各抒己见
  2. 批量处理ios破解后的资源文件为android所
  3. Android下的串口通信实战之控制客显
  4. android TV 开发规范
  5. Android APP存活检测
  6. Android的引用计数(强弱指针)技术及一些
  7. 怎么用Fuzz技术巧妙的挖掘Android漏洞?And
  8. 谈谈怎么最快学好android(转)
  9. Android NDK(七):JNI异常处理
  10. 花式读取Android CPU使用率