Android 自定义 ListView 显示网络上 JSON 格式歌曲列表

本文内容

  • 环境
  • 项目结构
  • 演示自定义 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

图 1 项目结构

 

演示自定义 ListView 显示网络上 JSON 歌曲列表


演示运行结果如下所示,本文只给出核心代码。点击此处下载,自己调试一下。

device-2014-07-07-160204

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

 

参考资料

这三个链接的关系是,第二个链接的演示是根据第一个链接完成的,第三个链接翻译的第二个链接。但遗憾的是,英文原文是有bug的,程序运行不稳定。

 

下载 Demo

你可能感兴趣的:(ListView)