这篇文章翻译自Ravi Tamada博客中的Android Custom ListView with Image and Text using Volley
最终效果
这个ListView呈现了一些影视信息,每一行是一个影片的信息,每一行中有一张电影的图片,电影的名字、评分、类型、年份等信息。
1.json数据
我们通过解析json然后拿到数据,这个json数据包括json数组,每个json数组中是一个json对象,json对象中包括了电影的图片url地址、标题、年份、评分、类型等信息
JSON Url:http://api.androidhive.info/json/movies.json
[
{
"title": "Dawn of the Planet of the Apes",
"image": "http://api.androidhive.info/json/movies/1.jpg",
"rating": 8.3,
"releaseYear": 2014,
"genre": ["Action", "Drama", "Sci-Fi"]
},
....
....
]
2.下载Volley库(volley.jar)
如果你第一次使用Volley框架,我建议你去我之前的文章看一下Android网络框架-Volley(一) 工作原理分析 。然后到百度上下载一个volley.jar。添加到项目的lib文件夹里面
3.布局分析
我选择了RelativeLayout来实现这个布局,图片我们使用volley提供的NetworkImageView
现在我们来新建一个Android项目
4.创建一个新的项目
1.打开eclipse,点击File-->New-->Android Application Project。填好基本信息后,我们把包名命名为info.androidhive.customlistviewvolley
2.将volley.jar添加到项目的lib文件夹下
3.我们先把包建好,我们一共分为4个包: adapter, app, model 和 util 。现在我们项目结构如下:
info.androidhive.customlistviewvolley.adater
info.androidhive.customlistviewvolley.app
info.androidhive.customlistviewvolley.model
info.androidhive.customlistviewvolley.util
4.打开res/values/colors.xml。如果没有colors.xml,我们就自己创建一个。然后添加如下代码
<?xml version="1.0" encoding="utf-8"?>
#666666#888888#d9d9d9#ffffff#ffffff#ebeef0#ebeef0
5.打开res/values/dimens.xml。添加如下代码
17dp15dip13dip12dip
6.在写jsva代码之前,我们先完成UI部分,在res下新建一个drawable文件夹,在res/drawable中新建3个xml文件:list_row_bg.xml、list_row_bg_hover.xml 、list_row_selector.xml 。
list_row_bg.xml -没有被点击时listview的样式
<?xml version="1.0" encoding="utf-8"?>
list_row_bg_hover.xml -被点击后listview的样式
<?xml version="1.0" encoding="utf-8"?>
list_row_selector.xml -切换两种样式的slector文件
<?xml version="1.0" encoding="utf-8"?>
7.打开activity_main.xml 添加listview
8.创建每个item的布局文件list_row.xml
<?xml version="1.0" encoding="utf-8"?>
UI部分我们已经完成了,接下来是java代码部分
9.在util包下新建LruBitmapCache.java 这个类是用来缓存图片的,这个类我们在之前文章中已经分析过了。参见Android网络框架-Volley(二) RequestQueue源码分析以及建立一个RequestQueue
package info.androidhive.customlistviewvolley.util;
import com.android.volley.toolbox.ImageLoader.ImageCache;
import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
public class LruBitmapCache extends LruCache implements
ImageCache {
public static int getDefaultLruCacheSize() {
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
final int cacheSize = maxMemory / 2;
return cacheSize;
}
public LruBitmapCache() {
this(getDefaultLruCacheSize());
}
public LruBitmapCache(int sizeInKiloBytes) {
super(sizeInKiloBytes);
}
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
@Override
public Bitmap getBitmap(String url) {
return get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
put(url, bitmap);
}
}
10.在app包下新建AppController.java 这个类是用来创建一个单例RequestQueue的,以及初始化一些volley核心对象
package info.androidhive.customlistviewvolley.app;
import info.androidhive.customlistviewvolley.util.LruBitmapCache;
import android.app.Application;
import android.text.TextUtils;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;
public class AppController extends Application {
public static final String TAG = AppController.class.getSimpleName();
private RequestQueue mRequestQueue;
private ImageLoader mImageLoader;
private static AppController mInstance;
@Override
public void onCreate() {
super.onCreate();
mInstance = this;
}
public static synchronized AppController getInstance() {
return mInstance;
}
public RequestQueue getRequestQueue() {
if (mRequestQueue == null) {
mRequestQueue = Volley.newRequestQueue(getApplicationContext());
}
return mRequestQueue;
}
public ImageLoader getImageLoader() {
getRequestQueue();
if (mImageLoader == null) {
mImageLoader = new ImageLoader(this.mRequestQueue,
new LruBitmapCache());
}
return this.mImageLoader;
}
public void addToRequestQueue(Request req, String tag) {
// set the default tag if tag is empty
req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
getRequestQueue().add(req);
}
public void addToRequestQueue(Request req) {
req.setTag(TAG);
getRequestQueue().add(req);
}
public void cancelPendingRequests(Object tag) {
if (mRequestQueue != null) {
mRequestQueue.cancelAll(tag);
}
}
}
11.现在我们要在 AndroidManifest.xml 中注册这个AppController,并且添加上网络权限
<?xml version="1.0" encoding="utf-8"?>
12.现在在model包下创建一个Movie实体类,解析完的json数据会保存到这个实体类中
package info.androidhive.customlistviewvolley.model;
import java.util.ArrayList;
public class Movie {
//title=标题, thumbnailUrl=图片地址
private String title, thumbnailUrl;
//年份
private int year;
//评分
private double rating;
//类别
private ArrayList genre;
public Movie() {
}
public Movie(String name, String thumbnailUrl, int year, double rating,
ArrayList genre) {
this.title = name;
this.thumbnailUrl = thumbnailUrl;
this.year = year;
this.rating = rating;
this.genre = genre;
}
public String getTitle() {
return title;
}
public void setTitle(String name) {
this.title = name;
}
public String getThumbnailUrl() {
return thumbnailUrl;
}
public void setThumbnailUrl(String thumbnailUrl) {
this.thumbnailUrl = thumbnailUrl;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public double getRating() {
return rating;
}
public void setRating(double rating) {
this.rating = rating;
}
public ArrayList getGenre() {
return genre;
}
public void setGenre(ArrayList genre) {
this.genre = genre;
}
}
13.在adapter包下新建一个 CustomListAdapter.java adapter会将item布局加载出来,并且将数据显示到listview上面
package info.androidhive.customlistviewvolley.adater;
import info.androidhive.customlistviewvolley.R;
import info.androidhive.customlistviewvolley.app.AppController;
import info.androidhive.customlistviewvolley.model.Movie;
import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.NetworkImageView;
public class CustomListAdapter extends BaseAdapter {
private Activity activity;
private LayoutInflater inflater;
private List movieItems;
ImageLoader imageLoader = AppController.getInstance().getImageLoader();
public CustomListAdapter(Activity activity, List movieItems) {
this.activity = activity;
this.movieItems = movieItems;
}
@Override
public int getCount() {
return movieItems.size();
}
@Override
public Object getItem(int location) {
return movieItems.get(location);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (inflater == null)
inflater = (LayoutInflater) activity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (convertView == null)
convertView = inflater.inflate(R.layout.list_row, null);
if (imageLoader == null)
imageLoader = AppController.getInstance().getImageLoader();
NetworkImageView thumbNail = (NetworkImageView) convertView
.findViewById(R.id.thumbnail);
TextView title = (TextView) convertView.findViewById(R.id.title);
TextView rating = (TextView) convertView.findViewById(R.id.rating);
TextView genre = (TextView) convertView.findViewById(R.id.genre);
TextView year = (TextView) convertView.findViewById(R.id.releaseYear);
// getting movie data for the row
Movie m = movieItems.get(position);
// thumbnail image
thumbNail.setImageUrl(m.getThumbnailUrl(), imageLoader);
// title
title.setText(m.getTitle());
// rating
rating.setText("Rating: " + String.valueOf(m.getRating()));
// genre
String genreStr = "";
for (String str : m.getGenre()) {
genreStr += str + ", ";
}
genreStr = genreStr.length() > 0 ? genreStr.substring(0,
genreStr.length() - 2) : genreStr;
genre.setText(genreStr);
// release year
year.setText(String.valueOf(m.getYear()));
return convertView;
}
}
14.打开我们的MainActivity.java。添加如下代码,我们使用JsonArrayRequest来发送请求,发送json请求我们在Android网络框架-Volley(四) 使用get和post方法发送json请求 已经讲过了。我们将解析来的Movie对象存储在一个ArrayList中,调用notifyDataSetChanged()方法通知listview去更新我们的数据。
package info.androidhive.customlistviewvolley;
import info.androidhive.customlistviewvolley.adater.CustomListAdapter;
import info.androidhive.customlistviewvolley.app.AppController;
import info.androidhive.customlistviewvolley.model.Movie;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.widget.ListView;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.VolleyLog;
import com.android.volley.toolbox.JsonArrayRequest;
public class MainActivity extends Activity {
// 用来打Log日志的TAG
private static final String TAG = MainActivity.class.getSimpleName();
// JSON地址
private static final String url = "http://api.androidhive.info/json/movies.json";
private ProgressDialog pDialog;
//用来存储Movie对象的list
private List movieList = new ArrayList();
private ListView listView;
private CustomListAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.list);
adapter = new CustomListAdapter(this, movieList);
listView.setAdapter(adapter);
pDialog = new ProgressDialog(this);
// 加载框
pDialog.setMessage("Loading...");
pDialog.show();
// 发送一个Json请求
JsonArrayRequest movieReq = new JsonArrayRequest(url,
new Response.Listener() {
@Override
public void onResponse(JSONArray response) {
Log.d(TAG, response.toString());
hidePDialog();
// 解析json数据
for (int i = 0; i < response.length(); i++) {
try {
JSONObject obj = response.getJSONObject(i);
Movie movie = new Movie();
movie.setTitle(obj.getString("title"));
movie.setThumbnailUrl(obj.getString("image"));
movie.setRating(((Number) obj.get("rating"))
.doubleValue());
movie.setYear(obj.getInt("releaseYear"));
// Genre是一个json数组
JSONArray genreArry = obj.getJSONArray("genre");
ArrayList genre = new ArrayList();
for (int j = 0; j < genreArry.length(); j++) {
genre.add((String) genreArry.get(j));
}
movie.setGenre(genre);
// 将解析好的一个movie对象添加到list中
movieList.add(movie);
} catch (JSONException e) {
e.printStackTrace();
}
}
// 通知listview我们的数据已经改变,现在更新
adapter.notifyDataSetChanged();
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
VolleyLog.d(TAG, "Error: " + error.getMessage());
hidePDialog();
}
});
// 将request添加到requestQueue中
AppController.getInstance().addToRequestQueue(movieReq);
}
@Override
public void onDestroy() {
super.onDestroy();
hidePDialog();
}
private void hidePDialog() {
if (pDialog != null) {
pDialog.dismiss();
pDialog = null;
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}