自制Android 文件浏览器

以前手机上一直使用的文件浏览器是文件大师,但是这些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(){} 

}

ListModeXXX:用来标记状态的,主要的状态包括:浏览、编辑、打开文件,当我们需要对文件进行操作,诸如删除或者移动时就需要进入编辑状态,其余时候就是浏览状态了。

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;
	}

ViewHolder是一个包含了所有listviewitem上的控件的的,所以这里的成员有 CheckBox cbCheckBox和TextView tvFileName。

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) {}



	
}

updateFileList是更新m_ListView,让其显示当前目录的文件。另外还有其他一些辅助函数,这里没有写出来。


入口函数onCreate

	@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);

	}

首先,设置当前工作目录为根目录,然后,初始化Actvity的ContentView,也就是界面,之后找到界面中的ListView,并初始化它:

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);
	}

这里的逻辑比较简单,首先是更新m_Adapter的List,然后是MainActivity的ActionBar以及ListView。

删除文件:

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;
		}

长按某一项,就会切换ListView的状态为ListMode_Edit,显示Remove和Move按钮,隐藏back按钮。
5、FileUtil和MToolBox
FileUitl中定义了程序中用到的文件操作的所有函数,包括获取文件类表,返回单签工作目录,删除文件,根据路径返回文件名,以及根据文件名和文件类型对文件进行排序。
MToolBox中定义的就是一些杂七杂八的函数。
6、Global
Global类中定义的是在程序中所要用到的常量,包括根目录,文件类型。
7、今后的工作
现在我的程序还有很多bug,但是能够满足基本的需要,今后我会增加更多的功能,最终的想法是,还能通过内网或者蓝牙传送文件。另外还要让我的软件更加漂亮和和谐。
       由于我的软件还很简陋,就不贴图了。


Author E-mail:[email protected]

 
  

你可能感兴趣的:(应用开发)