以前手机上一直使用的文件浏览器是文件大师,但是这些android的软件经常提示更新,然后还有很多附加功能,根本用不上,以及一些讨厌的通知。
作为一个程序员理应动手解决这个烦恼,因此我编写了下面的文件浏览器。我的程序用的比较特殊的功能总结如下:
1、Intent.addCategory和Uri;
2、TextView的CompoundDrawable;
3、ListView的重绘;
4、BaseAdapter的继承;
5、各种Listener的实现;
6、ActionBar的实现和改变;
7、带ProgressBar的dialog。
一、环境配置
我选择的target 是android 4.0, 估计android 3.0以前的程序用不了。
二、Mainifest文件
文件中只配置了一个Activity,使用的权限有
第一个权限主要是操作存储器上的文件,没有它甚至不能看到文件。
第二个权限主要是使用Intent。
三、source code
源代码可以在github上下载。下载下来的源码是不能导入到eclipse的,要运行的话先创建一个工程:
工程名:FileExplorer
包名:com.huneng.fileexplorer
然后把下载的源码中的src,res,和AndroidManifest复制到新建的工程中,文件选择覆盖。
下面就依次介绍我的想法
1、首先是建立界面
界面上主要用到的控件是ListView,ListView 的Item也要自己设计,目前这个Item上的控件有TextView和Checkable。ListView和ListViewItem分别在文件activity_main.xml和listitem中设计
2、自定义Adapter
如果只是简单的Item,例如item内容只是string,是不需要自定义Adapter。我希望我的FileExplorer功能复杂一点所以就定义了Adapter:
public class MyAdapter extends BaseAdapter {
public static final int ListMode_Edit = 0;
public static final int ListMode_Default = 1;
public static final int ListMode_Open = 2;
private List m_sFileNames;
private boolean m_CheckArray[];
public int m_ListMode;
private Context m_Context;
public MyAdapter(Context a_Context) {}
public MyAdapter(Context a_Context, List a_FileNames) {}
@Override
public int getCount() {}
@Override
public Object getItem(int a_Index) {}
@Override
public long getItemId(int a_Pos) {}
@Override
public View getView(int a_Position, View a_ConvertView, ViewGroup a_Parent) {}
public List getFileNames() {}
public void setFileNames(List a_FileNames) {}
public boolean setCheck(int a_Index, boolean a_Value) {}
public void setListMode(int a_ListMode) {}
public List getCheckedList(){}
public List getUnCheckedList(){}
}
m_sFileNames:文件名
m_CheckArray:选择文件时的标记数组。
m_Context:MainActivity对象的Context,生成自定义item时需要用其生成LayoutInflater
这个类的主要函数是 :
@Override
public View getView(int a_Position, View a_ConvertView, ViewGroup a_Parent) {
if (m_sFileNames == null)
return null;
ViewHolder t_Holder = null;
if (a_ConvertView == null) {
t_Holder = new ViewHolder();
LayoutInflater l_Inflater = LayoutInflater.from(m_Context);
a_ConvertView = l_Inflater.inflate(R.layout.listitem, null);
t_Holder.tvFileName = (TextView) a_ConvertView
.findViewById(R.id.tv_FileName);
t_Holder.cbCheckBox = (CheckBox) a_ConvertView
.findViewById(R.id.cb_FileCheck);
a_ConvertView.setTag(t_Holder);
} else {
t_Holder = (ViewHolder) a_ConvertView.getTag();
}
String l_FilePath = m_sFileNames.get(a_Position);
String l_FileName = FileUtil.getFileName(l_FilePath);
t_Holder.tvFileName.setText(l_FileName);
int l_FileType = FileUtil.getFileType(l_FilePath);
int l_ResourceID = MToolBox.getFileImage(l_FileType);
Drawable l_Drawable = m_Context.getResources()
.getDrawable(l_ResourceID);
int x = 50 * MainActivity.m_xd;
l_Drawable.setBounds(0, 0, x - 5, x - 5);
t_Holder.tvFileName.setCompoundDrawablePadding(5);
t_Holder.tvFileName.setCompoundDrawables(l_Drawable, null, null, null);
if (m_ListMode == ListMode_Edit)
t_Holder.cbCheckBox.setVisibility(View.VISIBLE);
else
t_Holder.cbCheckBox.setVisibility(View.INVISIBLE);
t_Holder.cbCheckBox
.setOnCheckedChangeListener(new MyOnCheckedChangeListener(
m_CheckArray, a_Position));
t_Holder.cbCheckBox.setChecked(m_CheckArray[a_Position]);
return a_ConvertView;
}
FileUtil和ToolBox在之后会讲到;
MainActivity的m_xd是1dp的像素数,TextView有一个CompoundDrawables属性,指的是TextView的上下左右放置的图片,我是用了左边的图像用来标记文件类型,这里指支持目录、jpg和png,其他文件一概而论。
这个函数的最下面初始化了cbCheckBox,如果处在浏览状态,则让它不可见。自定义的MyOnCheckedChangeListener类实现了接口OnCheckedChangeListener,当cbCheckBox的check状态改变时会反映到m_CheckArray上。所以m_CheckArray要与m_sFileNames的数量保持一致。
3、MainActivity
类MainActivity继承自Activity,启用了ActionBar,因此在Menu/main.xml中有如下定义:
总共5项,返回上一级、删除、移动、取消和确定,他们的属性中android:showAsAction是控制其是否在ActionBar上显示的选项。
下面是MainActivity的定义:
public class MainActivity extends Activity {
MyAdapter m_Adapter;
ListView m_ListView;
Menu m_OptionMenu;
MenuItem m_RemoveItem;
MenuItem m_BackItem;
MenuItem m_CancelItem;
MenuItem m_OkItem;
MenuItem m_MoveItem;
List m_RenameList;
public static int m_xd, m_yd;
@Override
protected void onCreate(Bundle savedInstanceState) {}
public void updateFileList(String a_DirPath) {}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {}
@Override
public boolean onCreateOptionsMenu(Menu a_Menu) {}
@Override
public boolean onOptionsItemSelected(MenuItem a_Item) {}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FileUtil.setCurPath(GlobalVariable.ROOT_PATH);
setContentView(R.layout.activity_main);
ListView m_ListView = (ListView) findViewById(R.id.lv_FileList);
List l_List = FileUtil.getCurDirFileNames();
m_Adapter = new MyAdapter(this);
m_Adapter.setFileNames(l_List);
m_ListView.setAdapter(m_Adapter);
m_ListView.setOnItemClickListener(new MyOnItemClickListener(this));
m_ListView.setOnItemLongClickListener(new MyOnItemLongClickListener(this));
DisplayMetrics l_Metrics = new DisplayMetrics();
this.getWindowManager().getDefaultDisplay().getMetrics(l_Metrics);
m_xd = (int) (l_Metrics.widthPixels / l_Metrics.xdpi);
m_yd = (int) (l_Metrics.heightPixels / l_Metrics.ydpi);
}
1)得到当前目录下的文件;
2)创建MyAdapter对象m_Adapter,将文件路径列表传递给它;
3)讲m_Adapter传递给m_ListView;
最后,计算x轴和y轴1dp的像素数。
MenuItem选择的入口函数:
@Override
public boolean onOptionsItemSelected(MenuItem a_Item) {
switch (a_Item.getItemId()) {
case R.id.action_back:
String l_Path = FileUtil.getParentPath();
updateFileList(l_Path);
break;
case R.id.action_removefile:
List l_List = m_Adapter.getCheckedList();
for (int i = 0; i < l_List.size(); i++)
FileUtil.removeFile(l_List.get(i));
// m_Adapter.setListMode(MyAdapter.ListMode_Default);
List l_FileLists = FileUtil.getCurDirFileNames();
m_Adapter.setFileNames(l_FileLists);
m_Adapter.notifyDataSetChanged();
displayBackAction();
break;
case R.id.action_move:
displayJudgeActions();
m_RenameList = m_Adapter.getCheckedList();
m_Adapter.setListMode(MyAdapter.ListMode_Default);
m_Adapter.notifyDataSetChanged();
break;
case R.id.action_ok:
if (FileUtil.getCurPath().startsWith(MToolBox.getSdcardPath())) {
for (int i = 0; i < m_RenameList.size(); i++) {
String l_FilePath = m_RenameList.get(i);
String l_FileName = FileUtil.getFileName(l_FilePath);
FileUtil.renameFile(l_FilePath, FileUtil.getCurPath() + "/"
+ l_FileName);
}
} else {
Toast.makeText(this, "Can't operate file here",
Toast.LENGTH_LONG).show();
}
case R.id.action_cancel:
displayBackAction();
m_Adapter.setFileNames(FileUtil.getCurDirFileNames());
m_Adapter.notifyDataSetChanged();
break;
}
return super.onOptionsItemSelected(a_Item);
}
删除文件:
1)获取被选择的文件名,根据MyAdapter.m_sFileNames和MyAdapter.m_CheckArray确定,具体见MyAdapter的定义;
2)逐次删除文件,有可能删除的文件是目录,这样就会递归删除;
3)刷新m_ListView,这是m_Adapter.notifyDataSetChanged()函数的工作,告诉ListView重新构造自己。
4)更新ActionBar的MenuItem,隐藏removeItem,显示BackItem。
移动文件:
1)长按ListView的某一项,然后选择文件;
2)点击ActionBar上的Item,Move,将会进入默认状态,但是ActionBar上的按钮有区别;
3)选择目录,点击确定,移动就完成了。
类MyOnItemClickListener和类MyOnItemLongClickListener定义为MainAcitivity的内部类,主要是图方便。
public void onItemClick(AdapterView> a_AdapterView, View a_View,
int a_Pos, long a_Id) {
String l_FilePath = m_Parent.m_Adapter.getFileNames().get(a_Pos);
int l_Type = FileUtil.getFileType(l_FilePath);
if (l_Type == Global.FileType_Dir) {
m_Parent.updateFileList(l_FilePath);
} else if (l_Type == Global.FileType_Other) {
return;
} else {
FileUtil.openFile(l_FilePath, m_Parent);
}
}
点击到的文件是目录,就进入,文件的话,则打开,有些特殊的文件类型是打不开的。
public boolean onItemLongClick(AdapterView> a_AdapterView,
View a_View, int a_Pos, long a_id) {
m_Parent.displayEditActions();
CheckBox l_CheckBox = (CheckBox) a_View
.findViewById(R.id.cb_FileCheck);
l_CheckBox.setChecked(true);
m_Parent.updateFileList(FileUtil.getCurPath());
m_Parent.m_Adapter.setListMode(MyAdapter.ListMode_Edit);
return true;
}
public boolean onItemLongClick(AdapterView> a_AdapterView,
View a_View, int a_Pos, long a_id) {
m_Parent.displayEditActions();
CheckBox l_CheckBox = (CheckBox) a_View
.findViewById(R.id.cb_FileCheck);
l_CheckBox.setChecked(true);
m_Parent.updateFileList(FileUtil.getCurPath());
m_Parent.m_Adapter.setListMode(MyAdapter.ListMode_Edit);
return true;
}
Author E-mail:[email protected]