前段时间做了个九宫格文件管理器(GridView实现,1920*1080分辨率,定制Android,目前只在MTK和MSTAR平台上测试及完善过),由于此工程为公司项目中的子项目,代码就不公开了(不过,实现不难,以下只提供主要代码处理和实现考虑情况)
/storage为根路径
静默安装应用(过程中会有类ProgressBar滚动直到完成操作或操作失败。静默安装过程放在线程中以防止UI卡顿或者因为权限等原因造成ANR使进程被Kill(原生安卓只有在极端情况下才会kill进程,相关源码:ActivityManagerService.java中管理Activity的代码))
静默卸载应用(同上)
以上仅供参考学习!其实文件管理器实现起来很简单,只不过过多的细节需要进行考虑而已①。还是得一边做一边在脑海中与原生的文件管理器模拟对比,逐渐完善。
①细节包括:
1. 文件管理器中安装应用后,返回到文件管理器时焦点位置是否要记录还原,如果不还原文件过多时可能要翻好多下才能找到安装的apk文件。
2.在正在使用的存储介质拔出时,提示并只能退出。提示控件对话框(以下叫dialog)截获onKeyDown事件致使点一次返回响应两次返回操作的问题:先dialog消失,接着Activity响应back事件,也就是事件冲突和截获,可以了解一下onKeyDown返回值代表的意思。而真实的路径已经不存在,所以即使按返回,dialog要消失且程序要有“待机”界面或不友好点直接退出。
3.文件或文件夹可能会有权限问题要处理,在清单文件AndroidManifest.xml中配置android:sharedUserId=”android.uid.system”让应用程序成为系统应用,但最好的做法还是区分文件和文件夹进行相同的提示,但给予不同的操作。
其实,一分析,有很多的…还是要以用户的角度去做东西。
好了,直接上代码,在这里只放上写的Demo的Adaper部分供大家学习:
BaseAdapter.java
public class GridAdapter extends BaseAdapter {
private LayoutInflater inflater;
private Bitmap directory, file_notinstall, file_hasinstall,backBitmap;
private ArrayList<String> names = null;
private ArrayList<String> paths = null;
private ArrayList<String> states = null;
public Context getmContext() {
return mContext;
}
public void setmContext(Context mContext) {
this.mContext = mContext;
}
public ArrayList<String> getNames() {
return names;
}
public void setNames(ArrayList<String> names) {
this.names = names;
}
public ArrayList<String> getPaths() {
return paths;
}
public void setPaths(ArrayList<String> paths) {
this.paths = paths;
}
public ArrayList<String> getStates() {
return states;
}
public void setStates(ArrayList<String> states) {
this.states = states;
}
public GridAdapter(Context context, ArrayList<String> name,
ArrayList<String> path, ArrayList<String> state) {
names = name;
paths = path;
states = state;
if (this.mContext == null)
this.mContext = context;
BitmapFactory.Options opts = new BitmapFactory.Options();
//缩放比例,缩放比为原图的1/n。
opts.inSampleSize = 1;
//每个像素占用2byte内存(565没有透明度属性),默认ARGB_8888一个像素4byte
opts.inPreferredConfig = Bitmap.Config.RGB_565;
//要真正做到大数量的列表,可以采用分页式的数据显示,或者使用LruCache缓存图片
//(分页的原理也就是只显示和加载当前屏幕中显示到的数据,即滑动事件中监听并对边缘的判断和处理),
//其实这里可以不用每个都判断是否为空,
//而是这四种图片之一拿出来判断即可,因为它们都是同时初始化和同时释放资源的,且不是多线程运行。
if (directory == null)
directory = BitmapFactory.decodeResource(context.getResources(),
R.drawable.directory, opts);
if (file_notinstall == null)
file_notinstall = BitmapFactory.decodeResource(
context.getResources(), R.drawable.file_nodownload, opts);
if (file_hasinstall == null)
file_hasinstall = BitmapFactory.decodeResource(
context.getResources(), R.drawable.filehasdownload, opts);
if (backBitmap == null)
backBitmap = BitmapFactory.decodeResource(context.getResources(),
R.drawable.back, opts);
if (inflater == null)
inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return names.size();
}
@Override
public Object getItem(int position) {
return names.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (null == convertView) {
convertView = inflater.inflate(R.layout.file, null);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.ItemText);
holder.image = (ImageView) convertView.findViewById(R.id.ItemImage);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
File f = new File(paths.get(position).toString());
if (names.get(position).equals("@HOME")) {//保留
holder.text.setText("/");
holder.image.setImageBitmap(directory);
} else if (names.get(position).equals("@BACK")) {//返回上一页
holder.text.setText("..");
holder.image.setImageBitmap(backBitmap);
} else {
holder.text.setText(f.getName());
if (f.isDirectory()) {
holder.image.setImageBitmap(directory);
} else if (f.isFile()) {
if (states.get(position).equals(KeyStringConst.APK_HASINSTALL)) {
holder.image.setImageBitmap(file_hasinstall);
} else if (states.get(position).equals(
KeyStringConst.APK_NOINSTALL)) {
holder.image.setImageBitmap(file_notinstall);
}
} else {
System.out.println(f.getName());
}
}
return convertView;
}
//缓存每个item的模版类
private class ViewHolder {
private TextView text;
private ImageView image;
}
private Bitmap small(Bitmap map, float num) {
Matrix matrix = new Matrix();
matrix.postScale(num, num);
return Bitmap.createBitmap(map, 0, 0, map.getWidth(), map.getHeight(),
matrix, true);
}
}
如上Adapter部分的代码所示,图片的inflate只产生一次。而在Activity中需要更新界面时代码如下:
private void showFileDir(String path) {
...
if (adapter != null) {//避免多次new GridAdapter(),而直接修改参数并通过adapter适配界面。
//而先判断不为空,因为大多情况下adapter都不为空,如果是if-else if-...-else结构,主体部分放在靠前的位置判断,
//能够省去不必要的判断。虽然这对于程序执行而言没有明显改善,但要是从机器语言执行顺序角度思考这绝对是好的习惯。
adapter.setNames(names);
adapter.setPaths(paths);
adapter.setStates(states);
adapter.notifyDataSetChanged();//Notifies the attached observers that the underlying data has been changed and any View reflecting the data set should refresh itself.
} else {
adapter = new GridAdapter(getApplicationContext(), names, paths,
states);
gridView.setAdapter(adapter);
}
...
}
以上就是我的项目的文件结构。
以上是我觉得要写出来与大家学习的部分,其实文件管理器实现不难,多的是细节的处理,用户体验和优化得做好。最后来一张最终效果图: