Android編寫簡(jiǎn)易文件管理模塊
最近在做一個(gè)將word文檔導(dǎo)入到SQLite的程序。對(duì)于文件選擇問題,經(jīng)過再三考慮決定寫一個(gè)簡(jiǎn)易的文件管理模塊,用來選擇需要導(dǎo)入的文件文件
先看下效果圖:

思路:
獲取存儲(chǔ)器接口
遍歷當(dāng)前目錄
利用ListView顯示文件文件夾
先是布局
<?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" android:orientation="vertical"> <HorizontalScrollView android:layout_width="match_parent" android:layout_height="wrap_content" android:scrollbars="none"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" android:id="@+id/lyPath"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:textAppearance" android:text="@string/txt_path_now"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:textAppearance" android:text="mnt/sdcard" android:id="@+id/txtPath"/> </LinearLayout> </HorizontalScrollView> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="@android:color/darker_gray"/> <LinearLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"> <ListView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/folderList"/> </LinearLayout> </LinearLayout>
用于加載文件的Item布局
list_file_style.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:scaleY="0.9"> <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content" android:focusable = "false" android:focusableInTouchMode="false" android:id="@+id/cbSelect"/> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/img" android:src="@mipmap/other" tools:ignore="ContentDescription" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:textAppearance" android:id="@+id/name" android:text="setting"/> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/time" android:text="2017-3-30 21:40:02"/> <View android:layout_width="20dp" android:layout_height="match_parent"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/size" android:text="1.2Mb"/> </LinearLayout> </LinearLayout> </LinearLayout> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="@android:color/darker_gray"/> </LinearLayout>
自定義類
為了更好的將數(shù)據(jù)綁定到ListView上我選擇自定義BaseAdapter類
package czhy.grey.sun.exam.bin.adapter_;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import java.io.File;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import czhy.grey.sun.exam.R;
import czhy.grey.sun.exam.bin.holder_.FileHolder;
public class FileAdapter extends BaseAdapter {
private ArrayList<File> list;
private LayoutInflater inflater;
private boolean isRoot;
// 用來控制CheckBox的選中狀況
private HashMap<Integer, Boolean> isSelected;
private int selectNum;
public FileAdapter(Context context, ArrayList<File> list,boolean isRoot) {
this.list = list;
this.isRoot = isRoot;
inflater = LayoutInflater.from(context);
isSelected = new HashMap<>();
// 初始化數(shù)據(jù)
initDate();
}
@Override
public int getCount() {
return list.size();
}
@Override
public File 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) {
FileHolder holder;
File file = getItem(position);
if (convertView == null) {
convertView = inflater.inflate(R.layout.list_file_style, parent, false);
holder = new FileHolder(convertView);
convertView.setTag(holder);
} else {
holder = (FileHolder) convertView.getTag();
}
// TODO: 2017/4/1 根目錄UI優(yōu)化
if (!isRoot && position == 0) {
holder.setName("返回上一層", file.isDirectory(),isSelectedFor(position));
holder.setId(position,isSelectedFor(position),new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = (int) v.getTag();
boolean b = !isSelected.get(position);
isSelected.put(position, b);
((CheckBox) v).setChecked(b);
//全選或全取消操作
for(int i=0;i< getCount();i++){
setChecked(i,b);
}
selectNum = b?getCount():0;
notifyDataSetChanged();
}
});
holder.setTime("全選");
holder.setSize("已選擇"+selectNum+"項(xiàng)");
} else {
holder.setName(file.getName(), file.isDirectory(),isSelectedFor(position));
holder.setId(position,isSelectedFor(position),new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = (int) v.getTag();
boolean b = !isSelectedFor(position);
isSelected.put(position, b);
((CheckBox) v).setChecked(b);
//是否已經(jīng)全選
if(isSelectedAll()) {
isSelected.put(0, true);
selectNum = getCount();
}else {
isSelected.put(0, false);
selectNum = b?selectNum+1:selectNum-1;
}
notifyDataSetChanged();
}
});
holder.setTime(new SimpleDateFormat("yyyy/mm/hh/dd hh:mm:ss").format(file.lastModified()));
if (file.isFile())
holder.setSize(fileLength(file.length()));
else {
holder.setSize("");
}
}
return convertView;
}
public boolean isSelectedFor(int id) {
return isSelected.get(id);
}
public int getSelectNum() {
return selectNum;
}
//私有函數(shù)
/** 初始化isSelected的數(shù)據(jù)
*/
private void initDate() {
selectNum = 0;
for (int i = 0; i < list.size(); i++) {
isSelected.put(i, false);
}
}
private void setChecked(int id,boolean isChecked) {
isSelected.put(id,isChecked);
}
private String fileLength(long length) {
String size;
if (length > 1024 * 1024)
size = new DecimalFormat("#.00").format(length / (1024.0 * 1024.0)) + "MB";
else if (length > 1024)
size = new DecimalFormat("#.00").format(length / 1024.0) + "KB";
else
size = length + "B";
return size;
}
private boolean isSelectedAll(){
for(int i=1;i< getCount();i++){
if(!isSelectedFor(i))
return false;
}
return true;
}
}
以及用于布局導(dǎo)入的Holder
package czhy.grey.sun.exam.bin.holder_;
import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.TextView;
import czhy.grey.sun.exam.R;
public class FileHolder{
private CheckBox cbSelect;
private TextView name;
private TextView time;
private TextView size;
private ImageView img;
public FileHolder(View convertView) {
cbSelect = (CheckBox)convertView.findViewById(R.id.cbSelect);
name = (TextView)convertView.findViewById(R.id.name);
time = (TextView)convertView.findViewById(R.id.time);
img = (ImageView)convertView.findViewById(R.id.img);
size = (TextView)convertView.findViewById(R.id.size);
}
public void setTime(String time) {
this.time.setText(time);
}
public void setId(int id,boolean isSelected, View.OnClickListener listener) {
cbSelect.setTag(id);
cbSelect.setChecked(isSelected);
cbSelect.setOnClickListener(listener);
}
public void setName(String name,boolean isDirectory,boolean isChecked) {
this.name.setText(name);
cbSelect.setChecked(isChecked);
if (isDirectory) {
if(name.equalsIgnoreCase("返回上一層")){
img.setImageResource(R.mipmap.back);
}else
img.setImageResource(R.mipmap.folder);
}else {
String type = name.substring(name.lastIndexOf(".")+1,name.length());
if ((type.equalsIgnoreCase("doc") || type.equalsIgnoreCase("docx") || type.equalsIgnoreCase("txt"))) {
img.setImageResource(R.mipmap.doc_text);
} else {
img.setImageResource(R.mipmap.other);
}
}
}
public void setSize(String size) {
this.size.setText(size);
}
}
控制文件
全局變量
private ListView folderList; private TextView txtPath; private FileAdapter fileAdapter; private ArrayList<File> rootFileList; private boolean isRootNow;
首先是獲取存儲(chǔ)器列表
private void getRootFile(){
rootFileList = new ArrayList<>();
StorageManager storageManager = (StorageManager) this.getSystemService(STORAGE_SERVICE);
try {
//內(nèi)部存儲(chǔ)器
File inside = null;
//可移除存儲(chǔ)器集合
ArrayList<File> outside = new ArrayList<>();
//獲取存儲(chǔ)器接口 API-24以下不支持StorageVolume接口
//API-24開始可直接 List<StorageVolume> svList = storageManager.getStorageVolumes();
Method getVolumeList = StorageManager.class.getMethod("getVolumeList");
getVolumeList.setAccessible(true);
//獲取存儲(chǔ)器列表
Object[] invokes = (Object[]) getVolumeList.invoke(storageManager);
if (invokes != null) {
for (Object obj:invokes) {
//獲取存儲(chǔ)器地址接口
Method getPath = obj.getClass().getMethod("getPath");
//獲取存儲(chǔ)器地址
String path = (String) getPath.invoke(obj);
File file = new File(path);
if (file.canWrite()) {
//獲取存儲(chǔ)器是否可移除接口
Method isRemovable = obj.getClass().getMethod("isRemovable");
//存儲(chǔ)器是否可移除
if((isRemovable.invoke(obj)).equals(true)){
outside.add(file);
}else {
inside = file;
}
}
}
//按0-內(nèi)部存儲(chǔ)器 >0外部存儲(chǔ)器 順序 添加到根目錄列表
rootFileList.add(inside);
rootFileList.addAll(outside);
}
} catch (NoSuchMethodException |
IllegalArgumentException |
IllegalAccessException |
InvocationTargetException e1) {
e1.printStackTrace();
}
}
當(dāng)我們點(diǎn)擊一個(gè)文件夾時(shí),打開文件夾以及更新UI
//獲取目錄數(shù)據(jù)
private void refurbish(File folder) {
txtPath.setText(folder.getPath());
ArrayList<File> files = new ArrayList<>();
files.add(folder.getParentFile());
File[] folderFile = folder.listFiles();
if (null != folderFile && folderFile.length > 0) {
for(File file:folderFile)
files.add(file);
}
//新建集合用做打開文件夾
ArrayList<File> openedFolder = new ArrayList<>();
//獲取第一個(gè)文件夾 上一級(jí)文件夾
openedFolder.add(files.get(0));
//移除 上一級(jí)文件夾 剩下為當(dāng)前文件夾內(nèi)容
files.remove(0);
//排序 文件夾在前,然后按文件名排序
Collections.sort(files, new Comparator<File>() {
@Override
public int compare(File f1, File f2) {
if (f1.isDirectory()) {
if (f2.isDirectory()) {
return f1.getName().compareToIgnoreCase(f2.getName());
} else {
return -1;
}
} else if (f2.isDirectory()) {
return 1;
} else {
return f1.getName().compareToIgnoreCase(f2.getName());
}
}
});
//將排序完畢的內(nèi)容添加到目標(biāo)集合 目的:解決第一個(gè)文件夾不是上一層地址問題
openedFolder.addAll(files);
fileAdapter = new FileAdapter(this, openedFolder,folder.getParent() == null);
folderList.setAdapter(fileAdapter);
isRootNow = false;
}
程序剛運(yùn)行時(shí)需要加載存儲(chǔ)器列表,而不是某一文件夾,所有需要額外定義一個(gè)函數(shù)
//獲取根目錄數(shù)據(jù)
private void rootFile() {
txtPath.setText("/");
fileAdapter = new FileAdapter(this, rootFileList,true);
folderList.setAdapter(fileAdapter);
isRootNow = true;
}
因?yàn)榇鎯?chǔ)器掛載點(diǎn)的問題,返回上一層時(shí)不會(huì)返回到存儲(chǔ)器列表也就是/storage目錄
加上有些文件夾是空或者為系統(tǒng)文件的安全性考慮需要對(duì)其隱藏,在獲取存儲(chǔ)器列表是已經(jīng)完成了
但,如果直接讓其返回上一層會(huì)出現(xiàn)進(jìn)入不安全的目錄,所以需要對(duì)其進(jìn)行判斷是否是返回根目錄
public boolean isRootFile(File file) {
//經(jīng)過兩部不同的手機(jī)測(cè)試,這兩個(gè)目錄是可能的目錄
//如果不能正確返回可以自行測(cè)試
//測(cè)試方法:輸出父目錄,然后在這里添加或修改即可
return file.getParent().equalsIgnoreCase("/") || file.getParent().equalsIgnoreCase("/storage");
}
有了以上這些,在控制文件創(chuàng)建是直接調(diào)用相應(yīng)的函數(shù)即可
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_import);
txtPath = (TextView) findViewById(R.id.txtPath);
folderList = (ListView) findViewById(R.id.folderList);
folderList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
File file = fileAdapter.getItem(position);
//因?yàn)闉榈某绦蛑胁恍枰獙?duì)文件進(jìn)行其他操作,所有不做處理
//有需求的在這里添加else即可
if (file.isDirectory()) {
if (isRootNow)
refurbish(file);
else if (isRootFile(file))
rootFile();
else
refurbish(file);
}
}
});
getRootFile();
rootFile();
}
弄了這么多基本算是完成了,為了用戶友好,我對(duì)返回鍵進(jìn)行了重載
/**
* 監(jiān)聽物理按鍵
* 需要注意的是這個(gè)函數(shù)是有返回值的
* 返回值可以理解為
* true表示做了處理,就不提交給處理系統(tǒng)的back按鍵事件
* false則是提交給系統(tǒng)處理
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0)) {
if (!isRootNow) {
File file = fileAdapter.getItem(0);
if (isRootFile(file))
rootFile();
else
refurbish(file);
return true;
} else {
finish();
}
}
return super.onKeyDown(keyCode, event);
}
參考文獻(xiàn):
如何獲取Android設(shè)備掛載的所有存儲(chǔ)器
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android安裝apk文件并適配Android 7.0詳解
這篇文章主要介紹了Android安裝apk文件并適配Android 7.0詳解的相關(guān)資料,需要的朋友可以參考下2017-05-05
Android自定義ViewGroup實(shí)現(xiàn)堆疊頭像的點(diǎn)贊Layout
這篇文章主要介紹了 Android自定義ViewGroup實(shí)現(xiàn)堆疊頭像的點(diǎn)贊Layout,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10
Android實(shí)現(xiàn)多線程下載文件的方法
這篇文章主要介紹了Android實(shí)現(xiàn)多線程下載文件的方法,以實(shí)例形式較為詳細(xì)的分析了Android多線程文件傳輸及合并等操作的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10
Android中Intent機(jī)制詳解及示例總結(jié)(總結(jié)篇)
本文是小編日常收集整理些有關(guān)Android中Intent機(jī)制詳解及示例總結(jié),對(duì)android中intent相關(guān)知識(shí)感興趣的朋友一起學(xué)習(xí)吧2016-04-04
Android接入支付寶實(shí)現(xiàn)支付功能實(shí)例
這篇文章主要介紹了Android接入支付寶實(shí)現(xiàn)支付功能實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06
詳解Android使用CoordinatorLayout+AppBarLayout實(shí)現(xiàn)拉伸頂部圖片功能
這篇文章主要介紹了Android使用CoordinatorLayout+AppBarLayout實(shí)現(xiàn)拉伸頂部圖片功能,本文實(shí)例文字相結(jié)合給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-10-10
Android camera實(shí)時(shí)預(yù)覽 實(shí)時(shí)處理,人臉識(shí)別示例
本篇文章主要介紹了Android camera實(shí)時(shí)預(yù)覽 實(shí)時(shí)處理,面部認(rèn)證示例,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-01-01
Android 個(gè)人理財(cái)工具二:使用SQLite實(shí)現(xiàn)啟動(dòng)時(shí)初始化數(shù)據(jù)
本文主要介紹 Android 使用SQLite實(shí)現(xiàn)啟動(dòng)時(shí)初始化數(shù)據(jù),這里對(duì)SQLite 的數(shù)據(jù)庫進(jìn)行詳解,附有示例代碼,有興趣的小伙伴可以參考下2016-08-08

