android ListView异步加载图片,完美实现图文混排

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 
         */  
        public Bitmap downloadImage() throws Exception {
            DefaultHttpClient client = new DefaultHttpClient();
            HttpGet getRequest = new HttpGet(imageUrl);
            HttpResponse response = client.execute(getRequest);
            HttpEntity entity = response.getEntity();
            InputStream is = null;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            try {
                is = entity.getContent();
                byte[] buf = new byte[1024];
                int readBytes = -1;
                while ((readBytes = is.read(buf)) != -1) {
                    baos.write(buf, 0, readBytes);
                }
            } finally {
                if (baos != null) {
                    baos.close();
                }
                if (is != null) {
                    is.close();
                }
            }
            byte[] imageArray = baos.toByteArray();
            return BitmapFactory.decodeByteArray(imageArray, 0, imageArray.length);
        }

    }  

}  

转载出处:http://blog.csdn.net/u012702547/article/details/49716677

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值