昨天参加一个面试,面试官让当场写一个类似于新闻列表的页面,文本数据和图片都从网络上获取,想起我还没写过ListView异步加载图片并实现图文混排效果的文章,so,今天就来写一下,介绍一下经验。
ListView加载文本数据都是很简单的,即使是异步获取文本数据。但是异步加载图片就稍微有一点麻烦,既要获得一个比较好的用户体验,还要防止出现图片错位等各种不良BUG,其实要考虑的东西还是挺多的。好了,我们先来看一下我们今天要实现的一个效果图:
看起来似乎并不难,确实,我们今天的核心问题只有一个,就是怎么异步加载图片,并且没有违和感。
好了,废话不多说,先来看主布局文件:
1
2
3
4
5
6
|
<relativelayout android:layout_height=
"match_parent"
android:layout_width=
"match_parent"
tools:context=
"com.example.listview.MainActivity"
xmlns:android=
"https://schemas.android.com/apk/res/android"
xmlns:tools=
"https://schemas.android.com/tools"
>
<listview android:id=
"@+id/lv"
android:layout_height=
"match_parent"
android:layout_width=
"match_parent"
>
</listview>
</relativelayout>
|
主布局中就一个listview,看看listview的item布局文件:
这个布局和我们上图描述的一样,左边一个ImageView,右边是两个TextView,这些都不难,我们看看MainActivity:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
public
class
MainActivity
extends
Activity {
private
ListView lv;
private
List<news> list;
private
String HTTPURL = https:
//litchiapi.jstv.com/api/GetFeeds?column=3&PageSize=20&pageIndex=1&val=100511D3BE5301280E0992C73A9DEC41;
private
Handler mHandler =
new
Handler(){
@Override
public
void
handleMessage(Message msg) {
super
.handleMessage(msg);
switch
(msg.what) {
case
0
:
MyAdaper adapter =
new
MyAdaper(list);
lv.setAdapter(adapter);
break
;
default
:
break
;
}
}
};
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lv = (ListView)
this
.findViewById(R.id.lv);
initData();
}
private
void
initData() {
list =
new
ArrayList<news>();
OkHttpClient client =
new
OkHttpClient();
Request request =
new
Request.Builder().url(HTTPURL).build();
Call call = client.newCall(request);
call.enqueue(
new
Callback() {
@Override
public
void
onResponse(Response response)
throws
IOException {
try
{
JSONObject jo1 =
new
JSONObject(response.body().string());
JSONObject jo2 = jo1.getJSONObject(paramz);
JSONArray ja = jo2.getJSONArray(feeds);
News news =
null
;
for
(
int
i =
0
; i < ja.length(); i++) {
JSONObject data = ja.getJSONObject(i).getJSONObject(
data);
String imageUrl = https:
//litchiapi.jstv.com
+ data.getString(cover);
String title = data.getString(subject);
String summary = data.getString(summary);
news =
new
News(imageUrl, title, summary);
list.add(news);
}
}
catch
(JSONException e) {
e.printStackTrace();
}
mHandler.obtainMessage(
0
).sendToTarget();
}
@Override
public
void
onFailure(Request arg0, IOException arg1) {
}
});
}
}</news></news>
|
在onCreate方法中,我们先拿到一个ListView的实例,然后就是初始化数据,这里初始化数据我们使用了OKHttp,关于OKHttp的使用可以查看我之前的文章OKHttp的简单使用,我们拿到一串json数据,至于json里边的结构是怎么样的,我就不多说了,大家可以直接在 浏览器中打开上面的地址,这样就能看到json数据了,我们把我们需要的数据封装成一个 JavaBean,其中ImageView我们先存储一个url地址,然后在Adapter中通过这个url地址异步加载图片。json解析就不多说了,我们瞅一眼这个Bean:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
public
class
News {
private
String imageUrl;
private
String title;
private
String summary;
public
String getImageUrl() {
return
imageUrl;
}
public
void
setImageUrl(String imageUrl) {
this
.imageUrl = imageUrl;
}
public
String getTitle() {
return
title;
}
public
void
setTitle(String title) {
this
.title = title;
}
public
String getSummary() {
return
summary;
}
public
void
setSummary(String summary) {
this
.summary = summary;
}
public
News(String imageUrl, String title, String summary) {
this
.imageUrl = imageUrl;
this
.title = title;
this
.summary = summary;
}
public
News() {
}
}
|
好了,到这里,所有的东西都还是基本用法,下面我们先不急着看Adapter,先来看看Google给我们提供的一个缓存机制,在android-support-v4.jar包中,Google提供了这样一个类LruCache,这个LruCache的使用和Java中的Map用法差不多,甚至你就可以把它当作Map来使用,不同的是LruCache中的Value可以是一张图片。如果我们缓存的图片太多,超出了我们设置的缓存大小,那么 系统会自动移除我们在最近使用比较少的图片。好了,我们来看看LruCache的定义:
1
|
private
LruCache<string, bitmapdrawable=
""
> mImageCache;</string,>
|
每个图片的缓存的key我们就使用该图片的url(这个是唯一的),value就是一张我们要缓存的图片,在实例化LruCache的时候,我们需要传入一个参数,表明我们可以使用的最大缓存,这个缓存参数我们传入可用缓存的1/8,同时我们需要重写sizeOf方法,查看 源码我们可以知道,如果不重写sizeOf方法,它默认返回的是图片的数量,但是我们实际上是需要计算图片大小来判断当前已经使用的缓存是否已经超出界限,所以我们这里重写sizeOf方法,返回每张图片的大小。代码如下:
1
2
3
4
5
6
7
8
|
int
maxCache = (
int
) Runtime.getRuntime().maxMemory();
int
cacheSize = maxCache /
8
;
mImageCache =
new
LruCache<string, bitmapdrawable=
""
>(cacheSize) {
@Override
protected
int
sizeOf(String key, BitmapDrawable value) {
return
value.getBitmap().getByteCount();
}
};</string,>
|
从LruCache中读取一张图片的方式和从Map中取值是一样的:
1
|
mImageCache.get(key)
|
向LruCache中存储一张图片:
1
|
mImageCache.put(key, bitmapDrawable);
|
关于LruCache的基本用法就说这些,这已经够我们后面使用了,现在我就大概说说我们的一个思路,当我们要给ImageView设置图片的时候,就先在本地缓存中查看是否有该图片,有的话,直接从本地读取,没有的话就从网络请求,同时,在从网络请求图片的时候,为了防止发生图片错位的情况,我们要给每一个item的每一个ImageView设置一个tag,这个tag就使用该ImageView要加载的图片的url(这样就可以确保每一个ImageView唯一),在给ImageView设置图片的时候我们就可以通过这个tag找到我们需要的ImageView,这样可以有效避免图片错位的问题。好了,看代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
public
class
MyAdaper
extends
BaseAdapter {
private
List<news> list;
private
ListView listview;
private
LruCache<string, bitmapdrawable=
""
> mImageCache;
public
MyAdaper(List<news> list) {
super
();
this
.list = list;
int
maxCache = (
int
) Runtime.getRuntime().maxMemory();
int
cacheSize = maxCache /
8
;
mImageCache =
new
LruCache<string, bitmapdrawable=
""
>(cacheSize) {
@Override
protected
int
sizeOf(String key, BitmapDrawable value) {
return
value.getBitmap().getByteCount();
}
};
}
@Override
public
int
getCount() {
return
list.size();
}
@Override
public
Object getItem(
int
position) {
return
list.get(position);
}
@Override
public
long
getItemId(
int
position) {
return
position;
}
@Override
public
View getView(
int
position, View convertView, ViewGroup parent) {
if
(listview ==
null
) {
listview = (ListView) parent;
}
ViewHolder holder =
null
;
if
(convertView ==
null
) {
convertView = LayoutInflater.from(parent.getContext()).inflate(
R.layout.listview_item,
null
);
holder =
new
ViewHolder();
holder.iv = (ImageView) convertView.findViewById(R.id.iv);
holder.title = (TextView) convertView.findViewById(R.id.title);
holder.summary = (TextView) convertView.findViewById(R.id.summary);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder) convertView.getTag();
}
News news = list.get(position);
holder.title.setText(news.getTitle());
holder.summary.setText(news.getSummary());
holder.iv.setTag(news.getImageUrl());
// 如果本地已有缓存,就从本地读取,否则从网络请求数据
if
(mImageCache.get(news.getImageUrl()) !=
null
) {
holder.iv.setImageDrawable(mImageCache.get(news.getImageUrl()));
}
else
{
ImageTask it =
new
ImageTask();
it.execute(news.getImageUrl());
}
return
convertView;
}
class
ViewHolder {
ImageView iv;
TextView title, summary;
}
class
ImageTask
extends
AsyncTask<string, bitmapdrawable=
""
> {
private
String imageUrl;
@Override
protected
BitmapDrawable doInBackground(String... params) {
imageUrl = params[
0
];
Bitmap bitmap = downloadImage();
BitmapDrawable db =
new
BitmapDrawable(listview.getResources(),
bitmap);
// 如果本地还没缓存该图片,就缓存
if
(mImageCache.get(imageUrl) ==
null
) {
mImageCache.put(imageUrl, db);
}
return
db;
}
@Override
protected
void
onPostExecute(BitmapDrawable result) {
// 通过Tag找到我们需要的ImageView,如果该ImageView所在的item已被移出页面,就会直接返回null
ImageView iv = (ImageView) listview.findViewWithTag(imageUrl);
if
(iv !=
null
&& result !=
null
) {
iv.setImageDrawable(result);
}
}
/**
* 根据url从网络上下载图片
*
* @return
*/
private
Bitmap downloadImage() {
HttpURLConnection con =
null
;
Bitmap bitmap =
null
;
try
{
URL url =
new
URL(imageUrl);
con = (HttpURLConnection) url.openConnection();
con.setConnectTimeout(
5
*
1000
);
con.setReadTimeout(
10
*
1000
);
bitmap = BitmapFactory.decodeStream(con.getInputStream());
}
catch
(MalformedURLException e) {
e.printStackTrace();
}
catch
(IOException e) {
e.printStackTrace();
}
finally
{
if
(con !=
null
) {
con.disconnect();
}
}
return
bitmap;
}
}
}</string,></string,></news></string,></news>
|
好了,listview图文混排就说到这里,有问题欢迎留言讨论。
摘自https://www.2cto.com/kf/201511/449384.html