Android – demo (四) 自定义弹框式文件选择器
简介:本文件选择器继承Dialog, 调用时如弹框一样;可筛选文件类型或仅文件夹,返回选中的文件或文件夹列表
项目链接:https://blog.csdn.net/weixin_43738911/article/details/109449345
效果演示:
使用方法:
public class xxx implements VirgoFileSelectorDialog.SelectFileCallback{
private VirgoFileSelectorDialog dialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dialog = new VirgoFileSelectorDialog(context);
dialog.setDialogHeight(ScreenUtil.getScreenHeight(context)/2);//设置弹框高度(显示屏高度一半)
dialog.setFlag(VirgoFileSelectorDialog.FLAG_FILE);//设置要选择的是文件还是文件夹
dialog.setFileType(VirgoFileSelectorDialog.TYPE_IMAGE);//选择dir后,无效
dialog.setRootPath("/storage/emulated/0");//设置根目录,若无效调用默认值
dialog.setCallback(this);//设置回调,必选,若要返回值
dialog.show();//显示
}
//从文件选择框返回的数据
@Override
public void onResult(List<File> list) {
Log.i(TAG, "onResult: ");
//deal with the result
}
}
源代码:
-
VirgoFileSelectorDialog.java
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iudvK0k2-1604916243827)(E:\ZSLWORK\media\fileSelector.gif)]/** * @author nepalese on 2020/10/30 15:20 * @usage 弹框式文件选择器,可筛选文件类型或仅文件夹,返回选中的文件或文件夹 * */ public class VirgoFileSelectorDialog extends Dialog implements ListView_FileSelector_Adapter.FileInterListener { private static final String TAG = "FileSelectorDialog"; //选择文件或文件夹 public static final int FLAG_DIR = 0; public static final int FLAG_FILE = 1; //仅显示某种特定文件 public static final String TYPE_ALL = "all"; public static final String TYPE_IMAGE = "image"; public static final String TYPE_TEXT = "txt"; public static final String TYPE_VIDEO = "video"; public static final String TYPE_AUDIO = "audio"; //选择特定文件类型后将显示的文件拓展名 public static final String[] IMAGE_EXTENSION = {"jpg", "jpeg", "png", "svg", "bmp", "tiff"}; public static final String[] AUDIO_EXTENSION = {"mp3", "wav", "wma", "aac", "flac"}; public static final String[] VIDEO_EXTENSION = {"mp4", "flv", "avi", "wmv", "mpeg", "mov", "rm", "swf"}; public static final String[] TEXT_EXTENSION = {"txt", "java", "html", "xml", "php"}; private static final String DEFAULT_ROOT_PATH = "/storage/emulated/0";//默认初始位置 private Context context; private SelectFileCallback callback; private TextView tvCurPath, tvConfirm; private LinearLayout layoutRoot, layoutLast; private ListView listView; private ListView_FileSelector_Adapter adapter; private String curPath;//当前路径 private List<File> files;//返回值 private List<Integer> index;//选中文件、夹索引 //默认设置 private int flag = FLAG_FILE;//默认选择文件 private String rootPath = DEFAULT_ROOT_PATH; private String fileType = TYPE_ALL; private int dialogHeight = 500;//默认弹框高度 public VirgoFileSelectorDialog(@NonNull Context context) { //默认自定义弹框样式,使用下面的构造函数可另设样式 this(context, R.style.File_Dialog); } public VirgoFileSelectorDialog(@NonNull Context context, int themeResId) { super(context, themeResId); setCancelable(false); init(context); } private void init(Context context) { this.context = context; LayoutInflater mLayoutInflater = LayoutInflater.from(context); View view = mLayoutInflater.inflate(R.layout.layout_file_selector, null); tvCurPath = view.findViewById(R.id.tvCurPath); tvConfirm = view.findViewById(R.id.tvConfirmChoose); layoutRoot = view.findViewById(R.id.layoutToRoot); layoutLast = view.findViewById(R.id.layoutToLast); listView = view.findViewById(R.id.listViewFile); index = new ArrayList<>(); setContentView(view); setListener(); } //============================================================================================== //初始化数据 private void setData() { setLayout();//设置弹框布局 curPath = rootPath; tvCurPath.setText(curPath); files = new ArrayList(getFiles(curPath)); adapter = new ListView_FileSelector_Adapter(context, files, this);//指向的是最开始的list // listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); listView.setAdapter(adapter); } private void setLayout() { Window dialogWindow = this.getWindow(); WindowManager.LayoutParams lp = dialogWindow.getAttributes(); dialogWindow.setGravity(Gravity.CENTER); /* * lp.x与lp.y表示相对于原始位置的偏移. * 当参数值包含Gravity.LEFT时,对话框出现在左边,所以lp.x就表示相对左边的偏移,负值忽略. * 当参数值包含Gravity.RIGHT时,对话框出现在右边,所以lp.x就表示相对右边的偏移,负值忽略. * 当参数值包含Gravity.TOP时,对话框出现在上边,所以lp.y就表示相对上边的偏移,负值忽略. * 当参数值包含Gravity.BOTTOM时,对话框出现在下边,所以lp.y就表示相对下边的偏移,负值忽略. * 当参数值包含Gravity.CENTER_HORIZONTAL时 * ,对话框水平居中,所以lp.x就表示在水平居中的位置移动lp.x像素,正值向右移动,负值向左移动. * 当参数值包含Gravity.CENTER_VERTICAL时 * ,对话框垂直居中,所以lp.y就表示在垂直居中的位置移动lp.y像素,正值向右移动,负值向左移动. * gravity的默认值为Gravity.CENTER,即Gravity.CENTER_HORIZONTAL | * Gravity.CENTER_VERTICAL. */ // lp.width = 300; // 宽度 lp.height = dialogHeight; // 高度 lp.alpha = 0.85f; // 透明度 Log.i(TAG, "setLayout: height= " + lp.height); dialogWindow.setAttributes(lp); } //进入新的路径或返回上一层,刷新数据 private void resetData(String path){ //若为空文件夹,则不进入 File file = new File(path); if(file.listFiles()==null || file.listFiles().length==0){ SystemUtil.showToast(context, "空文件夹!"); return; } curPath = path; tvCurPath.setText(curPath); files.clear(); index.clear();//重置选中 //note: 若直接使用 files = getFiles(curPath);listView 将无变化 List<File> temp = getFiles(curPath); for(File f : temp){ files.add(f); } adapter.notifyDataSetChanged(); } //根据条件筛选显示文件 private List<File> getFiles(String path){ if(flag==FLAG_DIR){ FileFilter filter = File::isDirectory; return Arrays.asList(new File(path).listFiles(filter)); }else if(flag==FLAG_FILE){//显示所有文件夹及选择的类型的文件 switch (fileType){ case TYPE_ALL: File[] fs = new File(path).listFiles(); Arrays.sort(fs, new Comparator<File>() { @Override public int compare(File o1, File o2) { return o1.getName().compareTo(o2.getName()); } }); return Arrays.asList(fs); case TYPE_AUDIO: return getCertainFile(path, AUDIO_EXTENSION); case TYPE_IMAGE: return getCertainFile(path, IMAGE_EXTENSION); case TYPE_TEXT: return getCertainFile(path, TEXT_EXTENSION); case TYPE_VIDEO: return getCertainFile(path, VIDEO_EXTENSION); } } return new ArrayList<>(); } private List<File> getCertainFile(String path, String[] extension){ List<File> list = new ArrayList<>(); File[] files = new File(path).listFiles();; //排序 Arrays.sort(files, new Comparator<File>() { @Override public int compare(File o1, File o2) { return o1.getName().compareTo(o2.getName()); } }); List extList = Arrays.asList(extension); for (File file:files){ if (file.isDirectory()){ list.add(file); continue; } if (extList.contains(getExtensionName(file.getName()))){ list.add(file); } } return list; } private String getExtensionName(String filename) { if ((filename != null) && (filename.length() > 0)) { int dot = filename.lastIndexOf('.'); if ((dot > -1) && (dot < (filename.length() - 1))) { return filename.substring(dot + 1).toLowerCase(); } } return ""; } //清空数据 private void release() { files.clear(); index.clear(); adapter = null; } //===============================================外部调用api===================================== @Override public void show() { super.show(); setData(); } @Override public void dismiss() { super.dismiss(); release(); } //设置根目录 public void setRootPath(String rootPath) { File file = new File(rootPath); if(file.exists() && file.isDirectory()){ this.rootPath = rootPath; } } //设置选择文件或文件夹 public void setFlag(int flag) { this.flag = flag; } //设置要筛选文件类型 public void setFileType(String fileType) { this.fileType = fileType; } public void setCallback(SelectFileCallback callback) { this.callback = callback; } public void setDialogHeight(int dialogHeight) { this.dialogHeight = dialogHeight; } @Override public boolean onKeyDown(int keyCode, KeyEvent event){ if(keyCode == KeyEvent.KEYCODE_BACK){ this.cancel(); } return super.onKeyDown(keyCode, event); } //添加或移除选择对象 @Override public void itemClick(View v, boolean isChecked) { Integer position = (Integer) v.getTag(); switch (v.getId()){ case R.id.cbChoose: if(isChecked){ index.add(position); Log.i(TAG, "add: " + position); }else{ index.remove(position); Log.i(TAG, "remove: " + position); } break; } } private void setListener() { listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Log.d("click", String.valueOf(position+1)); //judge file/dir if(files.get(position).isFile()){ //do nothing //可增加本地打开查看 }else{ //点击,进入文件夹 resetData(files.get(position).getPath()); } } }); tvConfirm.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(index.size()<1) { //未选择, 退出 dismiss(); return; } List<File> result = new ArrayList<>(); switch (flag){ case FLAG_DIR://return dirs for(int i=0; i<index.size(); i++){ //双重保险 if(files.get(index.get(i)).isDirectory()){ result.add(files.get(index.get(i))); } } break; case FLAG_FILE://return files for(int i=0; i<index.size(); i++){ if(files.get(index.get(i)).isFile()){ result.add(files.get(index.get(i))); } } break; } //通过回调函数返回结果 if(callback!=null){ callback.onResult(result); } //退出 dismiss(); } }); //返回上一级 layoutLast.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //judge curPath is root or not if (curPath.equals(rootPath)) { //do nothing SystemUtil.showToast(context, "已是根目录"); }else{ //back to last layer resetData(curPath.substring(0, curPath.lastIndexOf("/"))); } } }); //返回根目录 layoutRoot.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(curPath.equals(rootPath)){ //do nothing SystemUtil.showToast(context, "已是根目录"); }else{ //back to root resetData(rootPath); } } }); } //自定义回调接口: public interface SelectFileCallback{ void onResult(List<File> list); } }
-
ListView_FileSelector_Adapter.java
public class ListView_FileSelector_Adapter extends BaseAdapter { private Context context; private LayoutInflater inflater; private List<File> data; private List<CheckBean> beans = new ArrayList<>();//记录checkbox的选中情况 private FileInterListener interListener;//供外部引用接口 public ListView_FileSelector_Adapter(Context context, List<File> data, FileInterListener interListener){ this.context = context; inflater = LayoutInflater.from(context); this.data = data; this.interListener = interListener; for (int i = 0; i < data.size(); i++) { CheckBean bean = new CheckBean(i, false); beans.add(bean); } } @Override public int getCount() { return data==null ? 0:data.size(); } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return 0; } static class ViewHolder { public LinearLayout layout; public TextView tvData; public ImageView imageView; public CheckBox checkBox; } @Override public View getView(final int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = inflater.inflate(R.layout.layout_file_selector_list, null); holder = new ViewHolder(); holder.layout = convertView.findViewById(R.id.layout_all); holder.tvData = convertView.findViewById(R.id.tvFilePath); holder.imageView = convertView.findViewById(R.id.imgFileDir); holder.checkBox = convertView.findViewById(R.id.cbChoose); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.layout.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); String path = data.get(position).getPath(); holder.tvData.setText(path.substring(path.lastIndexOf("/")+1));//show the last layer if(data.get(position).isDirectory()){ holder.imageView.setImageResource(R.drawable.icon_dir); }else{ //file String tail = path.substring(path.lastIndexOf(".")+1); switch(tail.toLowerCase()){ case "mp3": case "wav": case "mp4": holder.imageView.setImageResource(R.mipmap.icon_selector_media); break; case "jpg": case "png": Glide.with(context).load(path).into(holder.imageView); break; default: holder.imageView.setImageResource(R.mipmap.icon_selector_file); break; } } //make component be able click from outside //防止CheckBox因滚动ListView时混乱 holder.checkBox.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { beans.get(position).setChecked(!beans.get(position).isChecked()); interListener.itemClick(view, beans.get(position).isChecked()); holder.checkBox.setChecked(beans.get(position).isChecked()); } }); holder.checkBox.setChecked(beans.get(position).isChecked()); holder.checkBox.setTag(position); return convertView; } public interface FileInterListener { void itemClick(View v, boolean isChecked); } }
-
CheckBean.java
public class CheckBean { private int id; private boolean isChecked; public CheckBean(int id, boolean isChecked) { this.id = id; this.isChecked = isChecked; } public int getId() { return id; } public boolean isChecked() { return isChecked; } public void setId(int id) { this.id = id; } public void setChecked(boolean checked) { isChecked = checked; } }
布局:
-
layout_file_selector.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="@dimen/padding_5" android:orientation="horizontal"> <TextView android:id="@+id/tvCurPath" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="curPath" android:textSize="@dimen/text_size_18" android:textColor="@color/black" android:singleLine="true" android:ellipsize="middle"/> <TextView android:id="@+id/tvConfirmChoose" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="@dimen/padding_5" android:text="确定" android:textColor="@color/colorRed" android:background="@drawable/selector_button_transparent"/> </LinearLayout> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/colorGery"/> <LinearLayout android:id="@+id/layoutBack" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <LinearLayout android:id="@+id/layoutToRoot" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:background="@drawable/selector_button_transparent"> <ImageView android:layout_width="30dp" android:layout_height="30dp" android:padding="@dimen/padding_3" app:srcCompat="@drawable/ic_file_root" tools:ignore="VectorDrawableCompat" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:layout_gravity="center_vertical" android:text="返回根目录" android:textSize="@dimen/text_size_16" android:textColor="@color/colorBlack" /> </LinearLayout> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/colorGery"/> <LinearLayout android:id="@+id/layoutToLast" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:background="@drawable/selector_button_transparent"> <ImageView android:layout_width="30dp" android:layout_height="30dp" android:padding="@dimen/padding_3" app:srcCompat="@drawable/ic_file_back" tools:ignore="VectorDrawableCompat" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:layout_gravity="center_vertical" android:text="返回上一层" android:textSize="@dimen/text_size_16" android:textColor="@color/colorBlack" /> </LinearLayout> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/colorGery"/> </LinearLayout> <ListView android:id="@+id/listViewFile" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1"/> </LinearLayout>
-
layout_file_selector_list.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="36dp" android:padding="@dimen/padding_3" android:id="@+id/layout_all" android:orientation="horizontal"> <ImageView android:id="@+id/imgFileDir" android:layout_width="30dp" android:layout_height="30dp" android:padding="@dimen/padding_3" /> <TextView android:id="@+id/tvFilePath" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginLeft="@dimen/margin_5" android:layout_weight="1" android:ellipsize="middle" android:singleLine="true" android:text="***" android:textColor="@color/colorBlack" android:textSize="@dimen/text_size_16" /> <CheckBox android:id="@+id/cbChoose" android:layout_width="30dp" android:layout_height="30dp" android:focusable="false" /> </LinearLayout>
-
style.xml
<style name="File_Dialog" parent="@android:style/Theme.Dialog"> <item name="android:windowIsFloating">true</item> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowNoTitle">true</item> <item name="android:background">#ffffff</item> <item name="android:backgroundDimEnabled">true</item> </style>