情境
如何寫一個檔案總管(FilesManager),其實少了很多功能,只是單純能夠看而已,所以我們要加上新增資料夾,刪除檔案,修改檔名的功能。
完整程式碼
你可以到 GitHub 上面觀看或下載完整程式碼。
程式碼說明
由於要加入儲存裝置的存取,因此,您需要加入一些權限。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
因為 6.0 會有 Runtime Permission 的問題,所以要有權限的控管,可以參考如何使用Runtime Permission,不過,這不是這篇的重點,因此,你只需要到設定->應用程式->權限內把權限打開就好 。
因為根目錄沒有存取權限,因此我們把ROOT改成可讀寫的路徑,在Android有存取權限的部分改成以下程式碼。
private static String ROOT = Environment.getExternalStorageDirectory().getAbsolutePath();
關於存取檔案可以參考如何在Android存讀檔案。
首先來製造一些對話框,用來負責產出跟使用者對話的內容,如果不會 AlertDialog,您可以參考如何使用AlertDialog這篇文章。
回到我們的程式,首先,建立一個選單列表,宣告一個字串陣列。
private static final String[] ACTION = {"修改", "刪除"};
對 ListView 增加一個 ItemLongClick 的事件。
listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
new AlertDialog.Builder(MainActivity.this)
.setItems(ACTION, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String name = ACTION[which];
Toast.makeText(MainActivity.this, name, Toast.LENGTH_SHORT).show();
}
})
.show();
return true;
}
});
回傳 true 代表事件到我這層就處理掉,不繼續往下傳,
否則會在觸發onClick的事件。
當長按某個檔案或資料夾,就會彈跳出AlertDialog的表單,讓你選擇剛剛宣告的字串陣列。
這意味著我們必須針對兩種情況進行判斷,因此,可以改寫成這樣。
new AlertDialog.Builder(MainActivity.this)
.setItems(ACTION, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch(which){
case 0://修改
break;
case 1://刪除
break;
}
}
})
.show();
接著在 main_activity.xml 增加一個新增資料夾的 TextView 按鈕。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.givemepass.filemanagerdemo.MainActivity">
<TextView
android:layout_alignParentBottom="true"
android:id="@+id/new_dir"
android:gravity="center"
android:layout_weight="1"
android:text="@string/new_dir"
android:layout_width="match_parent"
android:layout_height="20dp" />
<ListView
android:layout_above="@id/new_dir"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/list_view" />
</RelativeLayout>
你可以看到下面檔案列表多了一個按鈕。
新增資料夾
當我們按下按鈕以後,就可以新增一個資料夾。
final View item = LayoutInflater.from(MainActivity.this).inflate(R.layout.add_new_dir, null);
new AlertDialog.Builder(MainActivity.this)
.setTitle(R.string.input_dir_name)
.setView(item)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
EditText editText = (EditText) item.findViewById(R.id.edittext);
if(editText.getText().equals("")){return;}
String filePath = nowPath + File.separator + editText.getText().toString();
File f = new File(filePath);
Toast.makeText(MainActivity.this, filePath, Toast.LENGTH_SHORT).show();
if(f.mkdir()){
Toast.makeText(MainActivity.this, getString(R.string.create_dir_success) + filePath, Toast.LENGTH_SHORT).show();
getFileDirectory(nowPath);
simpleAdapter.notifyDataSetChanged();
} else{
Toast.makeText(MainActivity.this, R.string.create_dir_fail, Toast.LENGTH_SHORT).show();
}
}
})
.show();
首先會判斷 EdiText 內的字串是否有輸入,如果是空字串代表使用者按下新增資料夾卻沒有輸入資料夾名稱,此時,我們會直接 return,如果有,我們就可以直接處理,由於我可以可以透過目前的位置來新增一個資料夾,因此,下面這一行就可以先塞入File的建構子。
String filePath = nowPath + File.separator + editText.getText().toString();
如此來就可以透過 File 類別的方法來新增資料夾。
f.mkdir()
mkdir() 這個方法會回傳一個 boolean 值,用來判斷檔案是否有新增成功,如果有則會回傳 true,否則就回傳 false。
getFileDirectory(nowPath);
simpleAdapter.notifyDataSetChanged();
當新增資料夾成功,我們再一次的撈取該層資料夾以及檔案,再透過 adapter 來更新畫面,如此一來就可以成功看到資料夾是否有更新成功或失敗。
如下圖操作,按下新增按鈕。
輸入資料夾名稱。
如果新增成功可以看到畫面有更新一個資料夾,且跳出一個提示訊息顯示成功。
刪除資料夾或檔案
有時候你會想要把某些資料夾或者檔案刪除,因此,我們也要提供一個刪除的功能,而當要刪除的時候,要讓使用者指定要刪除哪一個檔案或資料夾,則要判斷使用者按下哪一個,因此,我們選擇使用長按功能。
listview 本身就有提供長按的處理事件,還記得我們上面有顯示兩種功能表,一種是刪除,另一個是修改,所以我們透過長按事件來跳出彈跳視窗可以這樣寫。
listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, final int position, long id) {
new AlertDialog.Builder(MainActivity.this)
.setItems(ACTION, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String path = paths.get(position);
switch(which){
case 0:
break;
case 1:
delFile(path);
break;
}
}
})
.show();
return true;
}
});
首先,判斷出哪一個列被使用者長按。
String path = paths.get(position);
可以透過外部該層資料夾的 paths 陣列取出使用者按下哪一個位置,就可以抓到該列的資料夾路徑,透過AlertDialog彈出一個對話視窗。
透過 delFile 這個方法就可以將檔案刪除,將路徑傳入 delFile 方法。
new AlertDialog.Builder(MainActivity.this)
.setTitle(R.string.make_sure_del)
.setNegativeButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
File file = new File(path);
if(file.exists()){
if(file.delete()){
Toast.makeText(MainActivity.this, R.string.del_success, Toast.LENGTH_SHORT).show();
getFileDirectory(nowPath);
simpleAdapter.notifyDataSetChanged();
} else{
Toast.makeText(MainActivity.this, R.string.del_fail, Toast.LENGTH_SHORT).show();
}
} else{
Toast.makeText(MainActivity.this, R.string.file_is_not_exist, Toast.LENGTH_SHORT).show();
}
}
}).setPositiveButton(R.string.cancel, new DialogInterface.OnClickListener(){
@Override
public void onClick(DialogInterface dialog, int which) {
}
})
.show();
在這邊我們可以看到傳入一個字串,我們把它塞入到 File 建構子,透過 File 類別的 exists 方法來判斷該檔案是否存在,如果存在則可以呼叫 delete 方法來進行刪除,delete 方法會回傳一個 boolean,
來判斷是否刪除成功?
跟新增資料夾相同,如果刪除成功則重新讀取,且該曾目錄會更新畫面。
getFileDirectory(nowPath);
simpleAdapter.notifyDataSetChanged();
如此一來,你就可以看到下面的畫面,再一次的詢問是否要刪除檔案。
將剛剛新增的資料夾刪除了。
修改名稱
最後剩下修改名稱的這個功能,在前面我們 ListView 有實做長按功能,其中,在 item 0 的部分並未處理,這邊是用來處理重新命名的功能。
new AlertDialog.Builder(MainActivity.this)
.setItems(ACTION, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String path = paths.get(position);
switch(which){
case 0:
rename(path);
break;
case 1:
delFile(path);
break;
}
}
})
.show();
可以看到我們呼叫了 rename 這個方法,並且把我們要處理的路徑傳入這個方法。
final View item = LayoutInflater.from(MainActivity.this).inflate(R.layout.add_new_dir, null);
new AlertDialog.Builder(MainActivity.this)
.setTitle(R.string.input_you_rename)
.setView(item)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
EditText editText = (EditText) item.findViewById(R.id.edittext);
if(editText.getText().equals("")){
Toast.makeText(MainActivity.this, R.string.input_dir_name, Toast.LENGTH_SHORT).show();
return;
}
String newPath = nowPath + File.separator + editText.getText();
File f = new File(path);
if(f.renameTo(new File(newPath))){
Toast.makeText(MainActivity.this, R.string.modify_success, Toast.LENGTH_SHORT).show();
getFileDirectory(nowPath);
simpleAdapter.notifyDataSetChanged();
} else{
Toast.makeText(MainActivity.this, R.string.modify_fail, Toast.LENGTH_SHORT).show();
}
}
})
.show();
在這邊就跟新增資料夾雷同,首先,判斷是否輸入要變更的名稱
如果使用者沒有輸入 則直接 return,反之,就進行處理。
跟處理新資料夾不太一樣,變更名稱會先將原本的檔案或資料夾刪除掉,再新增一個檔案或資料夾,因此,要透過原有的檔案或資料夾來進行 rename 的方法。
String newPath = nowPath + File.separator + editText.getText();
File f = new File(path);
先將舊的檔案把變成一個 File 物件。
f.renameTo(new File(newPath))
再透過 rename 方法將新 File 物件丟給舊有的 File 物件,一樣會回傳一個 boolean 來判斷是否 rename 成功?
操作上選擇要修改。
跳出詢問視窗。
輸入要修改的名稱。
成功將 qqq 修改成 givemepass 資料夾 。
這樣就是一個簡易版的檔案總管了。