本文内容
- 环境
- 项目结构
- 演示自定义 ListView 显示网络上 JSON 歌曲列表
- 参考资料
本文最开始看的是一个国人翻译的文章,没有源代码可下载,根据文中提供的代码片段,自己新建的项目(比较可恶的是,没有图标图片资源,只能自己乱搞),但程序不是很稳定,有时能显示出列表中的缩略图,有时显示不出来,还在主线程访问了网络。但在文章评论中,作者给出英文原文链接,本来想这下没事了吧,结果下载源代码运行后,还是有问题~仔细看英文原文,原来他也是根据 Github 上一个项目搞的,只是添加了式样,以及显示完整的歌曲列表,包括歌曲名、歌手名、缩略图、时长。
看来只能自己研究了,式样用英文原文的,改变主要有两个,一个是英文原文的网络歌曲列表是 .xml 格式文件,本文采用 .json 格式;二是调整了网络访问。
环境
- Windows 2008 R2 64 位
- Eclipse ADT V22.6.2,Android 4.4.3
- SAMSUNG GT-I9008L,Android OS 2.2.2
项目结构
图 1 项目结构
演示自定义 ListView 显示网络上 JSON 歌曲列表
演示运行结果如下所示,本文只给出核心代码。点击此处下载,自己调试一下。
图 2 主程序
自定义式样
gradient_bg.xml、gradient_bg_hover.xml、list_selector.xml 以及 image_bg.xml,定义列表背景,选中和没选中的式样,以及缩略图的背景。
main.xml 主页面及其后台
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ListView
android:id="@+id/mylist"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:divider="#b5b5b5"
android:dividerHeight="1dp"
android:listSelector="@drawable/list_selector" />
<Button
android:id="@+id/btn_clear"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="清除缓存, 重新加载" />
</LinearLayout>
CustomizedListView.java
package com.example.androidhive;
import org.json.JSONArray;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.ListView;
public class CustomizedListView extends Activity {
String url = "http://files.cnblogs.com/liuning8023/Android_Music_Demo_json_array.xml"; ListView list;
LazyAdapter adapter;
/* Button btn; */ private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 0x123) { Request rq = new Request(); JSONArray jsonArr = rq.getJsonFromUrl(url);
list = (ListView) findViewById(R.id.mylist);
adapter = new LazyAdapter(CustomizedListView.this, this,
jsonArr);
list.setAdapter(adapter);
/* * btn = (Button) findViewById(R.id.btn_clear); * btn.setOnClickListener(new OnClickListener() { * * @Override public void onClick(View arg0) { * adapter.imageLoader.clearCache(); * adapter.notifyDataSetChanged(); } }); */ }
super.handleMessage(msg);
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
new Thread(new Runnable() {
@Override
public void run() {
Message msg = new Message(); msg.what = 0x123;
handler.sendMessage(msg);
}
}).start();
}
}
list_row.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_selector"
android:orientation="horizontal"
android:padding="5dip" >
<!-- ListRow Left sied Thumbnail image --> <LinearLayout android:id="@+id/thumbnail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="3dip"
android:layout_alignParentLeft="true"
android:background="@drawable/image_bg"
android:layout_marginRight="5dip">
<ImageView
android:id="@+id/list_image"
android:layout_width="50dip"
android:layout_height="50dip"
android:src="@drawable/rihanna"/>
</LinearLayout>
<!-- Title Of Song--> <TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/thumbnail"
android:layout_toRightOf="@+id/thumbnail"
android:text="Rihanna Love the way lie"
android:textColor="#040404"
android:typeface="sans"
android:textSize="15dip"
android:textStyle="bold"/>
<!-- Artist Name --> <TextView
android:id="@+id/artist"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:textColor="#343434"
android:textSize="10dip"
android:layout_marginTop="1dip"
android:layout_toRightOf="@+id/thumbnail"
android:text="Just gona stand there and ..." />
<!-- Rightend Duration --> <TextView
android:id="@+id/duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignTop="@id/title"
android:gravity="right"
android:text="5:45"
android:layout_marginRight="5dip"
android:textSize="10dip"
android:textColor="#10bcc9"
android:textStyle="bold"/>
<!-- Rightend Arrow --> <ImageView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/arrow"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"/>
</RelativeLayout>
LazyAdapter.java
package com.example.androidhive;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.content.Context;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
public class LazyAdapter extends BaseAdapter {
private Activity activity; private JSONArray data; private static LayoutInflater inflater = null;
public ImageLoader imageLoader; public LazyAdapter(Activity a, Handler handler, JSONArray jsonArr) { activity = a;
data = jsonArr;
inflater = (LayoutInflater) activity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
imageLoader = new ImageLoader(activity.getApplicationContext()); }
public int getCount() {
return data == null ? 0 : data.length();
}
public Object getItem(int position) {
return position; }
public long getItemId(int position) {
return position; }
public View getView(int position, View convertView, ViewGroup parent) {
View vi = convertView;
if (convertView == null)
vi = inflater.inflate(R.layout.list_row, null); JSONObject song = null; ImageView image = (ImageView) vi.findViewById(R.id.list_image);
TextView tvartist = (TextView) vi.findViewById(R.id.artist);
TextView tvtitle = (TextView) vi.findViewById(R.id.title);
TextView tvduration = (TextView) vi.findViewById(R.id.duration);
try { song = data.getJSONObject(position);
imageLoader.DisplayImage(song.getString("thumb_url"), image); tvartist.setText(song.getString("artist")); tvtitle.setText(song.getString("title")); tvduration.setText(song.getString("duration")); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace();
}
return vi; }
}
其他工具类
FileCache.java 在外存缓存图片
package com.example.androidhive;
import java.io.File;
import android.content.Context;
public class FileCache {
private File cacheDir; public FileCache(Context context) { // Find the dir to save cached images if (android.os.Environment.getExternalStorageState().equals( android.os.Environment.MEDIA_MOUNTED))
cacheDir = new File( android.os.Environment.getExternalStorageDirectory(),
"LazyList"); else cacheDir = context.getCacheDir();
if (!cacheDir.exists()) cacheDir.mkdirs();
}
public File getFile(String url) { // I identify images by hashcode. Not a perfect solution, good for the // demo. String filename = String.valueOf(url.hashCode());
// Another possible solution (thanks to grantland) // String filename = URLEncoder.encode(url); File f = new File(cacheDir, filename); return f; }
public void clear() {
File[] files = cacheDir.listFiles();
if (files == null)
return; for (File f : files) f.delete();
}
}
MemoryCache.java 在内存缓存图片
package com.example.androidhive;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import android.graphics.Bitmap;
import android.util.Log;
public class MemoryCache {
private static final String TAG = "MemoryCache";
private Map<String, Bitmap> cache=Collections.synchronizedMap( new LinkedHashMap<String, Bitmap>(10,1.5f,true));//Last argument true for LRU ordering
private long size=0;//current allocated size
private long limit=1000000;//max memory in bytes
public MemoryCache(){ //use 25% of available heap size setLimit(Runtime.getRuntime().maxMemory()/4);
}
public void setLimit(long new_limit){
limit=new_limit;
Log.i(TAG, "MemoryCache will use up to "+limit/1024./1024.+"MB");
}
public Bitmap get(String id){ try{ if(!cache.containsKey(id)) return null;
//NullPointerException sometimes happen here http://code.google.com/p/osmdroid/issues/detail?id=78 return cache.get(id); }catch(NullPointerException ex){ ex.printStackTrace();
return null;
}
}
public void put(String id, Bitmap bitmap){
try{ if(cache.containsKey(id)) size-=getSizeInBytes(cache.get(id));
cache.put(id, bitmap);
size+=getSizeInBytes(bitmap);
checkSize();
}catch(Throwable th){ th.printStackTrace();
}
}
private void checkSize() {
Log.i(TAG, "cache size="+size+" length="+cache.size());
if(size>limit){ Iterator<Entry<String, Bitmap>> iter=cache.entrySet().iterator();//least recently accessed item will be the first one iterated while(iter.hasNext()){ Entry<String, Bitmap> entry=iter.next();
size-=getSizeInBytes(entry.getValue());
iter.remove();
if(size<=limit) break; }
Log.i(TAG, "Clean cache. New size "+cache.size()); }
}
public void clear() {
try{ //NullPointerException sometimes happen here http://code.google.com/p/osmdroid/issues/detail?id=78 cache.clear();
size=0;
}catch(NullPointerException ex){ ex.printStackTrace();
}
}
long getSizeInBytes(Bitmap bitmap) { if(bitmap==null)
return 0; return bitmap.getRowBytes() * bitmap.getHeight(); }
}
ImageLoader.java 加载图片并缓存
package com.example.androidhive;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import android.os.Handler;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.widget.ImageView;
public class ImageLoader {
MemoryCache memoryCache = new MemoryCache(); FileCache fileCache;
private Map<ImageView, String> imageViews = Collections .synchronizedMap(new WeakHashMap<ImageView, String>()); ExecutorService executorService;
Handler handler = new Handler();// handler to display images in UI thread
public ImageLoader(Context context) { fileCache = new FileCache(context); executorService = Executors.newFixedThreadPool(5);
}
final int stub_id = R.drawable.stub; /* * 显示图片 若缓存存在,则显示,否则获取 */ public void DisplayImage(String url, ImageView imageView) {
imageViews.put(imageView, url);
Bitmap bitmap = memoryCache.get(url); // 根据 url 从内存获得图片 if (bitmap != null)
imageView.setImageBitmap(bitmap);
else { queuePhoto(url, imageView);
imageView.setImageResource(stub_id);
}
}
private void queuePhoto(String url, ImageView imageView) {
PhotoToLoad p = new PhotoToLoad(url, imageView); executorService.submit(new PhotosLoader(p)); }
private Bitmap getBitmap(String url) { File f = fileCache.getFile(url);
// from SD cache Bitmap b = decodeFile(f);
if (b != null)
return b; // from web try { Bitmap bitmap = null; URL imageUrl = new URL(url); HttpURLConnection conn = (HttpURLConnection) imageUrl
.openConnection();
conn.setRequestMethod("GET"); // Sets the flag indicating whether this URLConnection allows input. // conn.setDoInput(true); conn.setConnectTimeout(3000);
conn.setReadTimeout(3000);
// Flag to define whether the protocol will automatically follow // redirects or not. conn.setInstanceFollowRedirects(true); int response_code = conn.getResponseCode(); if (response_code == 200) { InputStream is = conn.getInputStream(); OutputStream os = new FileOutputStream(f); StreamUtils.CopyStream(is, os); os.close();
conn.disconnect();
bitmap = decodeFile(f);
return bitmap; } else { conn.disconnect();
return null;
}
} catch (Throwable ex) { ex.printStackTrace();
if (ex instanceof OutOfMemoryError) memoryCache.clear();
return null;
}
}
// decodes image and scales it to reduce memory consumption private Bitmap decodeFile(File f) { try { // decode image size BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; FileInputStream stream1 = new FileInputStream(f); BitmapFactory.decodeStream(stream1, null, o); stream1.close();
// Find the correct scale value. It should be the power of 2. final int REQUIRED_SIZE = 70; int width_tmp = o.outWidth, height_tmp = o.outHeight; int scale = 1; while (true) {
if (width_tmp / 2 < REQUIRED_SIZE || height_tmp / 2 < REQUIRED_SIZE)
break; width_tmp /= 2;
height_tmp /= 2;
scale *= 2;
}
// decode with inSampleSize BitmapFactory.Options o2 = new BitmapFactory.Options(); o2.inSampleSize = scale;
FileInputStream stream2 = new FileInputStream(f); Bitmap bitmap = BitmapFactory.decodeStream(stream2, null, o2); stream2.close();
return bitmap; } catch (FileNotFoundException e) { } catch (IOException e) { e.printStackTrace();
}
return null;
}
// Task for the queue private class PhotoToLoad {
public String url; public ImageView imageView; public PhotoToLoad(String u, ImageView i) { url = u;
imageView = i;
}
}
class PhotosLoader implements Runnable { PhotoToLoad photoToLoad;
PhotosLoader(PhotoToLoad photoToLoad) { this.photoToLoad = photoToLoad; }
@Override
public void run() {
try { if (imageViewReused(photoToLoad)) return; Bitmap bmp = getBitmap(photoToLoad.url);
memoryCache.put(photoToLoad.url, bmp);
if (imageViewReused(photoToLoad)) return; BitmapDisplayer bd = new BitmapDisplayer(bmp, photoToLoad); handler.post(bd);
} catch (Throwable th) { th.printStackTrace();
}
}
}
boolean imageViewReused(PhotoToLoad photoToLoad) { String tag = imageViews.get(photoToLoad.imageView);
if (tag == null || !tag.equals(photoToLoad.url))
return true;
return false;
}
// Used to display bitmap in the UI thread class BitmapDisplayer implements Runnable { Bitmap bitmap;
PhotoToLoad photoToLoad;
public BitmapDisplayer(Bitmap b, PhotoToLoad p) { bitmap = b;
photoToLoad = p;
}
public void run() {
if (imageViewReused(photoToLoad)) return; if (bitmap != null)
photoToLoad.imageView.setImageBitmap(bitmap);
else photoToLoad.imageView.setImageResource(stub_id);
}
}
public void clearCache() {
memoryCache.clear();
fileCache.clear();
}
}
Request.java 访问网络
package com.example.androidhive;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import org.json.JSONArray;
import org.json.JSONException;
public class Request {
public Request() { }
public JSONArray getJsonFromUrl(String urlStr) { try { URL url = new URL(urlStr); HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET"); // Sets the flag indicating whether this URLConnection allows input. // conn.setDoInput(true); conn.setConnectTimeout(3000);
conn.setReadTimeout(3000);
// Flag to define whether the protocol will automatically follow // redirects or not. conn.setInstanceFollowRedirects(true); int response_code = conn.getResponseCode(); if (response_code == 200) { InputStream in = conn.getInputStream(); InputStreamReader inputReader = new InputStreamReader(in);
BufferedReader buffReader = new BufferedReader(inputReader); String line = "", JsonStr = "";
while ((line = buffReader.readLine()) != null) {
JsonStr += line;
}
JSONArray jsonArray = new JSONArray(JsonStr); return jsonArray; } else { conn.disconnect();
return null;
}
} catch (UnsupportedEncodingException e) { e.printStackTrace();
} catch (IOException e) { e.printStackTrace();
} catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace();
}
return null;
}
}
StreamUtils.java 缓存时的I/O操作
package com.example.androidhive;
import java.io.InputStream;
import java.io.OutputStream;
public class StreamUtils {
public static void CopyStream(InputStream is, OutputStream os)
{ final int buffer_size=1024; try { byte[] bytes=new byte[buffer_size];
for(;;) { int count=is.read(bytes, 0, buffer_size);
if(count==-1) break; os.write(bytes, 0, count);
}
}
catch(Exception ex){} }
}
参考资料
- Github LazyList
- androidhive android-custom-listview-with-imgae-and-text
- androidhive android android-custom-listview-with-image-and-text 译文
这三个链接的关系是,第二个链接的演示是根据第一个链接完成的,第三个链接翻译的第二个链接。但遗憾的是,英文原文是有bug的,程序运行不稳定。
- Android ListView 和 ***Adapter 从本地/网络获取歌曲列表
- Android LazyList 从网络获取图片并缓存
下载 Demo