写这个例子的目的是供同事和开发Android的朋友学习借鉴。
这个版本不是凭空冒出来的,就像爱因斯坦做手工的故事(不知道的见这里)一样,还有更丑的“小凳子”,它们是:
参考它们可以了解版本的演进过程。可能降低学习曲线。
这个版本的首页:
点击条目,会弹出带图片的详细信息界面:
这里的图片是从维基百科直接下载的,并会缓存在本地。
另外,就是对横竖屏使用了不同的布局:
下面介绍一下实现的要点。
异步延时加载的处理
以前我都是自己写异步加载,比如用Java concurrent编写异步加载图片功能和android异步加载ListView中的图片。其实android自己有个AsyncTask,可以方便的做这个事情。这次尝试使用了一下。
我目前的基本理解是AsyncTask简化了异步开发。
因为如果自己写,需要创建线程执行后台操作,比如下载图片文件,然后还是要通过handler通知ui线程将图片文件显示到界面上。
AsyncTask提供了需要实现的方法,一些方法AsyncTask会创建线程执行,比如doInBackground,而onPostExecute方法,AsyncTask会自行调用handler通知ui线程执行。
这部分代码见DetailViewActivity的内部类:
private class ShowImageTask extends AsyncTask<String, Void, Void> {
private String fileName;
@Override
protected Void doInBackground(String… params) {
fileName = params[1] + ".jpg";File cacheFile = new File(getCacheDir(), fileName);
if (!cacheFile.exists()) {
//创建临时文件,用于下载图片使用
File tempFile = new File(getCacheDir(), "tmp");
HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(params[0]);
try {
HttpResponse response = client.execute(get);
FileOutputStream outputStream = new FileOutputStream(
tempFile);
InputStream inputStream = response.getEntity().getContent();
for (int i = inputStream.read(); i != -1; i = inputStream
.read()) {
outputStream.write(i);
}
inputStream.close();
outputStream.close();
/**
* 改名临时文件,改为对应河流id的jpg文件
* 这样做是为了防止图片下载不全的问题
*/
tempFile.renameTo(cacheFile);
} catch (Exception e) {
throw new RuntimeException(e);
}
}return null;
}@Override
protected void onPostExecute(Void result) {
try {
imageView.setImageDrawable(Drawable.createFromStream(
new FileInputStream(new File(getCacheDir(), fileName)),
"river.jpg"));
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
imageLoadProgressBar.setVisibility(View.GONE);
}
这里还需要注意的是,使用了httpclient api来下载图片,这部分如果不明白,可参见更简单的示例android编写访问http的代码。只不过这里是使字节流。
下载的图片放在哪里?这里没有选择放在sd卡这样的外存中,而是放在内存里。内存可存储在两个位置:
- 存放在应用的files目录下,不过不能直接操作目录,只能通过openFileOutput()操作文件流,当不存在的时候自动创建
- 存放在应用的cache目录下,可操作文件目录,通过getCacheDir()方法可获取目录File对象
在这里使用cache目录的方式,因为要操作文件并且涉及到文件改名等。
有关Content Provider和数据库
示例使用了Content Provider,而且是:
sqlite+Content Provider+CursorAdapter+Loader+ListView
示例中详细界面是Activity,设置了dialog的theme,看上去像对话框。这样做的考虑是结构简单,两个Activity是通过intent来松散耦合的。在intent中传递的是river的id。
这样,content provider需要能处理对集合的查询和对指定id的查询:
switch (uriMatcher.match(uri)) {//判断是访问river集合还是指定id访问单条记录
case ITEMS:
cursor = database.query("rivers", projection, selection,
selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
case ITEM:
cursor = database.query("rivers", projection, _ID + "="
+ uri.getPathSegments().get(1), selectionArgs, null, null,
sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
default:
throw new IllegalArgumentException("unknown uri: " + uri);
}
横竖屏的处理
这个处理其实很简单。基本思路是针对横竖屏有不同的布局文件,android会在配置变化自动识别并加载对应文件。
如图所示,可知目录的命名方法。
源代码见:
http://easymorse.googlecode.com/svn/tags/CustomListViewDemo-0.5.1/