昨天参加一个面试,面试官让当场写一个类似于新闻列表的页面,文本数据和图片都从网络上获取,想起我还没写过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. Cocos2D-X 学习笔记

    1. 3.4final控制台创建项目后,安卓编译会失败,必须手动把cocos/平台/andorid/java/src目录里的文件复制到安卓项目的src文件夹即可 2. 安卓的文件目录与win的略有不同 ...

  2. Javascript实现AutoComplete自动匹配功能

    功能分析: 避免客户端频繁的访问服务器,因此客户端需要一个timer,监听键盘按键间隔时间,300-600毫秒能够接受. 服务端对要查找的数据源如果不大的话,应该尽量缓存在服务端内存中,而不是每次查找 ...

  3. OC第二节 —— NSString和NSMutableString

    1.为什么需要NSString对象        答:在OC中创建字符串时,一般不使用C的方法,    因为C将字符串作为字符数组,所以在操作时会有很多不方便的地方,    在Cocoa中NSStri ...

  4. 【定时任务|开机启动】Windows Server 2008/2012 计划任务配置(任务计划程序)每分钟执行BAT

    打开计划任务快捷方式(在 “管理工具”内): C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Administrative Tools\Tas ...

  5. 请求WebMethod, Ajax 处理更加专注

    在WebForm下 开发ajax程序,需要借助于一般处理程序(*.ashx)或web服务(*.asmx),并且每一个ajax请求,都要建一个这样的文件,如此一来,如 果在一个项目中ajax程序多了,势 ...

  6. Knockout.Js案例二Working With Lists And Collections

    案例一:Foreach绑定 通常,您要生成重复的UI元素,特别是当显示列表,用户可以添加和删除元素.KO.JS让你轻松,使用的数组和foreach绑定. 在接下来的几分钟,您将构建一个动态UI保留席位 ...

  7. android Activity基类通用方法

    public class BaseActivity extends Activity { Resources res; // 通用资源缩写 @Override protected void onCre ...

  8. 带你走进rsync的世界

    导读 Rsync(remote synchronize)是一个远程数据同步工具,可通过LAN/WAN快速同步多台主机间的文件,也可以使用 Rsync 同步本地硬盘中的不同目录.rsync共有3种使用方 ...

  9. sql执行顺序

    SQL 不同于与其他编程语言的最明显特征是处理代码的顺序.在大数编程语言中,代码按编码顺序被处理,但是在SQL语言中,第一个被处理的子句是FROM子句,尽管SELECT语句第一个出现,但是几乎总是最后 ...

  10. 粒子系统模块(Particle System Modules40)

    粒子系统模块(Particle System Modules40) 粒子系统模块(忍者飞镖) 粒子系统(忍者飞镖)(Particle System (Shuriken)) 用模块描述粒子一段时间内的行 ...