昨天参加一个面试,面试官让当场写一个类似于新闻列表的页面,文本数据和图片都从网络上获取,想起我还没写过ListView异步加载图片并实现图文混排效果的文章,so,今天就来写一下,介绍一下经验。

ListView加载文本数据都是很简单的,即使是异步获取文本数据。但是异步加载图片就稍微有一点麻烦,既要获得一个比较好的用户体验,还要防止出现图片错位等各种不良BUG,其实要考虑的东西还是挺多的。好了,我们先来看一下我们今天要实现的一个效果图:

看起来似乎并不难,确实,我们今天的核心问题只有一个,就是怎么异步加载图片,并且没有违和感。

好了,废话不多说,先来看主布局文件:

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.listview.MainActivity" > <ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ListView> </RelativeLayout>

主布局中就一个listview,看看listview的item布局文件:

 <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="100dp" > <ImageView
android:id="@+id/iv"
android:layout_width="80dp"
android:layout_height="90dp"
android:layout_centerVertical="true"
android:padding="5dp"
android:src="@drawable/ic_launcher" /> <TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_toRightOf="@id/iv"
android:gravity="center_vertical"
android:text="人社部:养老转移已有初稿"
android:textSize="14sp"
android:textStyle="bold" /> <TextView
android:id="@+id/summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:layout_marginTop="8dp"
android:layout_toRightOf="@id/iv"
android:text="人社部:养老转移已有初稿"
android:textSize="12sp" /> </RelativeLayout>

这个布局和我们上图描述的一样,左边一个ImageView,右边是两个TextView,这些都不难,我们看看MainActivity:

 public class MainActivity extends Activity {  

     private ListView lv;
private List<News> list;
private String HTTPURL = "http://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 = "http://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) { }
});
}
}

在onCreate方法中,我们先拿到一个ListView的实例,然后就是初始化数据,这里初始化数据我们使用了OKHttp,关于OKHttp的使用可以查看我之前的文章OKHttp的简单使用,我们拿到一串json数据,至于json里边的结构是怎么样的,我就不多说了,大家可以直接在浏览器中打开上面的地址,这样就能看到json数据了,我们把我们需要的数据封装成一个JavaBean,其中ImageView我们先存储一个url地址,然后在Adapter中通过这个url地址异步加载图片。json解析就不多说了,我们瞅一眼这个Bean:

 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的定义:

 private LruCache<String, BitmapDrawable> mImageCache;  

每个图片的缓存的key我们就使用该图片的url(这个是唯一的),value就是一张我们要缓存的图片,在实例化LruCache的时候,我们需要传入一个参数,表明我们可以使用的最大缓存,这个缓存参数我们传入可用缓存的1/8,同时我们需要重写sizeOf方法,查看源码我们可以知道,如果不重写sizeOf方法,它默认返回的是图片的数量,但是我们实际上是需要计算图片大小来判断当前已经使用的缓存是否已经超出界限,所以我们这里重写sizeOf方法,返回每张图片的大小。代码如下:

 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();
}
};

从LruCache中读取一张图片的方式和从Map中取值是一样的:

 mImageCache.get(key) 

向LruCache中存储一张图片:

 mImageCache.put(key, bitmapDrawable);

关于LruCache的基本用法就说这些,这已经够我们后面使用了,现在我就大概说说我们的一个思路,当我们要给ImageView设置图片的时候,就先在本地缓存中查看是否有该图片,有的话,直接从本地读取,没有的话就从网络请求,同时,在从网络请求图片的时候,为了防止发生图片错位的情况,我们要给每一个item的每一个ImageView设置一个tag,这个tag就使用该ImageView要加载的图片的url(这样就可以确保每一个ImageView唯一),在给ImageView设置图片的时候我们就可以通过这个tag找到我们需要的ImageView,这样可以有效避免图片错位的问题。好了,看代码:

 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, Void, 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;
} } }

好了,listview图文混排就说到这里,有问题欢迎留言讨论。

Demo下载https://github.com/lenve/listview_PicText

ListView异步加载图片,完美实现图文混排的更多相关文章

  1. android listview 异步加载图片并防止错位

    网上找了一张图, listview 异步加载图片之所以错位的根本原因是重用了 convertView 且有异步操作. 如果不重用 convertView 不会出现错位现象, 重用 convertVie ...

  2. Android中ListView异步加载图片错位、重复、闪烁问题分析及解决方案

    我们在使用ListView异步加载图片的时候,在快速滑动或者网络不好的情况下,会出现图片错位.重复.闪烁等问题,其实这些问题总结起来就是一个问题,我们需要对这些问题进行ListView的优化. 比如L ...

  3. Android 实现ListView异步加载图片

    ListView异步加载图片是非常实用的方法,凡是是要通过网络获取图片资源一般使用这种方法比较好,用户体验好,下面就说实现方法,先贴上主方法的代码: package cn.wangmeng.test; ...

  4. 又优化了一下 Android ListView 异步加载图片

    写这篇文章并不是教大家怎么样用listview异步加载图片,因为这样的文章在网上已经有很多了,比如这位仁兄写的就很好: http://www.iteye.com/topic/685986 我也是因为看 ...

  5. Android的ListView异步加载图片时,错位、重复、闪烁问题的分析及解决方法

    Android ListView异步加载图片错位.重复.闪烁分析以及解决方案,具体问题分析以及解决方案请看下文. 我们在使用ListView异步加载图片的时候,在快速滑动或者网络不好的情况下,会出现图 ...

  6. listview异步加载图片并防止错位

    android listview 异步加载图片并防止错位 网上找了一张图, listview 异步加载图片之所以错位的根本原因是重用了 convertView 且有异步操作. 如果不重用 conver ...

  7. ListView异步加载图片

    ListView异步加载图片是非常实用的方法,凡是是要通过网络获取图片资源一般使用这种方法比较好,用户体验好,下面就说实现方法,先贴上主方法的代码: package cn.wangmeng.test; ...

  8. Listview 异步加载图片之优化篇(有图有码有解释)

    在APP应用中,listview的异步加载图片方式能够带来很好的用户体验,同时也是考量程序性能的一个重要指标.关于listview的异步加载,网上其实很多示例了,中心思想都差不多,不过很多版本或是有b ...

  9. Android之ListView异步加载图片且仅显示可见子项中的图片

    折腾了好多天,遇到 N 多让人崩溃无语的问题,不过今天终于有些收获了,这是实验的第一版,有些混乱,下一步进行改造细分,先把代码记录在这儿吧. 网上查了很多资料,发现都千篇一律,抄来抄去,很多细节和完整 ...

随机推荐

  1. BZOJ 1116: [POI2008]CLO

    1116: [POI2008]CLO Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 922  Solved: 514[Submit][Status][ ...

  2. 【mysql】【分组】后取每组的top2

    DROP TABLE IF EXISTS `tb1`; CREATE TABLE `tb1` ( `id` ) NOT NULL AUTO_INCREMENT, `a` ) DEFAULT NULL, ...

  3. JSP个人总结

    应用JSP技术开发动态网站 JSP基本语法 默认JSP: <%@ page language="java" contentType="text/html; char ...

  4. 求算符文法的FIRSTVT集的算法

    原理 数据结构 G = {'key':[v1,v2,v3],'key':[v1,v2,v3]}; VN = []; Vt = []; FirstVT = {'key':[v1,v2,v3],'key' ...

  5. android滑动基础篇 TouchView

    效果图: 代码部分: activity类代码: package com.TouchView; import android.app.Activity; import android.os.Bundle ...

  6. C#(SuperWebSocket)与websocket通信

    原文:C#(SuperWebSocket)与websocket通信 客户端代码 点击可以查看一些关于websocket的介绍 <!DOCTYPE html> <html> &l ...

  7. 【JAVAWEB学习笔记】05_jQuery基础

    晨读单词: toggle:切换 each:每个(遍历) append:追加(内部追加,将B追加到A的内部结尾处) appendTo:追加(内部追加,将A追加到B的内部结尾处) prepend:追加(内 ...

  8. Mac下Kali虚拟机与宿主机共享文件夹

    宿主机: 1.创建文件夹.测试文件 ZhangSan-MacBook-Air:~ zhangsan$ mkdir kalishare && cd kalishare/ ZhangSan ...

  9. DaTaX当成jar包当作第三方库启动的相关问题

    上一篇已经大致的将了本地状况下DaTaX的纯Java代码启动的过程 http://www.cnblogs.com/blogsofmy/p/8287637.html不了解的请点超链接 这次我们来说说文件 ...

  10. 求两个数之间的质数 -----------基于for循环 算法思想

    前端代码: <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.as ...