昨天参加一个面试,面试官让当场写一个类似于新闻列表的页面,文本数据和图片都从网络上获取,想起我还没写过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. 利用UIActivityController调用ios系统自带的分享功能,实现微信发布多图的功能

    通过一番查找以后找到一个类UIActivityController,可以调用系统的social.framework中的分享接口.看下面的图就知道了,这个还是挺常见的 微信发布多图 借鉴了CSDN上的一 ...

  2. iSCSI配置流程

    Windows群集两个节点:分别在SQL01和SQL02设置连接共享磁盘: 此前已经在存储服务器通过StarWind创建了三个虚拟磁盘:Quemon+data+backup:starwind安装请参考 ...

  3. BSA混合分离分析法

    BSA(Bulked Segregant Analysis)又称混合群体分离分析法,是利用极端性状进行功能基因挖掘的一种方法.主要思想是将两个具有极端性状的群体进行混池测序,比较两个群体在多态位点(S ...

  4. JS的构造函数

    //构造函数  //使自己的对象多次复制,同时实例根据设置的访问等级可以访问其内部的属性和方法  //当对象被实例化后,构造函数会立即执行它所包含的任何代码  function myObject(ms ...

  5. 自己动手开发jQuery插件

    因为工作需要,所以这几天琢磨了一下关于jQuery插件开发的问题,经过一天鏖战,终于完成自己动手做的第一个jQuery插件,对于俺这种见了css就蛋疼菊紧的人来说,一天时间8小时,保守估计有5个小时在 ...

  6. codeblocks+Mingw 下配置开源c++单元测试工具 google test

    google test 是google的c++开源单元测试工具,chrome的开发团队就是使用它. Code::Blocks 12.11(MinGW 4.7.1) (Windows版)Google T ...

  7. 雪峰配置的nginx

  8. Pcserver+oracle10g+rac

    成本的相对廉价,技术的成熟,功能的强大此方案将越来越受中小企业的青睐.     一.实验前准备 虚拟机版本:Vwareserver1.0.6 Linux版本:redhat5.5enterprise服务 ...

  9. Unity3D游戏制作(四)——Asset Server搭建

    本系列文章由 Amazonzx 编写,欢迎转载,转载请注明出处. http://blog.csdn.net/amazonzx/article/details/7980117 Asset Server是 ...

  10. [Unity3d][NGUI]打包NGUI预制件成Assetbundle 两种思路.

    http://www.58player.com/blog-2537-85030.html 接上文,项目中因为需要UI热更新,所以我使用了AssetBundle这个解决方案.               ...