使用Android Studio 练习RecyclerView 异步加载图片,解决图片乱序问题。

每次写这个东西 都不知道如何写开头。
…………………………………………………………………………………………………………………….

锲而不舍的学习,就象玉石上的雕刻一样,日子虽长,字迹依旧清晰;
不求甚解的读书,犹如沙石上的记录,后面正在写,前面已被风沙吹盖得无影无踪。

大家都知道ListView 异步加载图片会出现图片错乱现象,这个已经不是问题的问题已经被大家熟悉和解决了,不知道的请戳进来,那么RecyclerView传说是ListView的升级版,那么会不会 出现类似的问题呢。
我们带着问题去一探究竟。

  • 第一步:创建Demo 寻找答案。
    Activity的布局文件:
 <RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/id_recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

</RelativeLayout>

大家注意 RecyclerView 是support-v7 包中的。

1、Eclipse 开发者需要 下载 RecyclerView.jar
2、Android Studio 开发者需要 在App目录下的 build.gradle 的dependencies 添加: compile ‘com.android.support:recyclerview-v7:+’

Adapter的布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent">

    <ImageView
        android:id="@+id/id_imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:scaleType="fitXY"
        />
</LinearLayout>

MainActivity代码 :

package cn.com.zyh;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

public class MainActivity extends AppCompatActivity {

    private RecyclerView mRecyclerView;
    private LinearLayoutManager mLinearLayoutManager;
    private MyRecyclerViewAdapter mRecyclerViewAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mRecyclerView = (RecyclerView) findViewById(R.id.id_recyclerView);

        mLinearLayoutManager = new LinearLayoutManager(this);

        mLinearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);

        mRecyclerView.setLayoutManager(mLinearLayoutManager);

        mRecyclerViewAdapter = new MyRecyclerViewAdapter(this,Images.imageUrls);

        mRecyclerView.setAdapter(mRecyclerViewAdapter);


    }


}

说明:Activity中 针对RecyclerView 的 声明 赋值 使用;如果是ListView 可以这么使用,但是我们用的是RecyclerView ,所以 有点区别。它需要LinearLayoutManager来管理内部的视图排列方式。当然还需要一个Adapter ,这里我们自己写了一个Adapter。

MyRecyclerViewAdapter:

package cn.com.zyh;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.os.AsyncTask;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

import java.io.IOException;

/**
 * Created by zyh on 2015/9/15.
 */
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.ViewHolder> {

    private String [] mImages;
    private Context mContext;
    private LayoutInflater mInflater;

    public MyRecyclerViewAdapter(Context context,String [] imagesUrls){
        this.mContext= context;
        mImages = imagesUrls;
        mInflater = LayoutInflater.from(context);
    }

    @Override
    public MyRecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = mInflater.inflate(R.layout.recycler_view,parent,false);
        ViewHolder holder = new ViewHolder(view);
        holder.imageView = (ImageView)view.findViewById(R.id.id_imageView);
        return holder;
    }

    @Override
    public void onBindViewHolder(MyRecyclerViewAdapter.ViewHolder holder, int position) {
        String imageUrl= mImages[position];
        //执行下载操作
        DownLoadTask task = new DownLoadTask(holder.imageView);
        task.execute(imageUrl);
    }

    @Override
    public int getItemCount() {
        return mImages.length;
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        ImageView imageView;
        public ViewHolder(View itemView) {
            super(itemView);
        }
    }

    /**
     * 异步加载图片
     */
     class DownLoadTask extends AsyncTask<String ,Void,BitmapDrawable>{
         private ImageView mImageView;
         String url;
         public DownLoadTask(ImageView imageView){
             mImageView = imageView;
         }
         @Override
         protected BitmapDrawable doInBackground(String... params) {
             url = params[0];
             Bitmap bitmap = downLoadBitmap(url);
             BitmapDrawable drawable = new BitmapDrawable(mContext.getResources(),bitmap);
             return  drawable;
         }

         private Bitmap downLoadBitmap(String url) {
             Bitmap bitmap = null;
             OkHttpClient client = new OkHttpClient();
             Request request = new Request.Builder().url(url).build();
             try {
                 Response response = client.newCall(request).execute();
                 bitmap = BitmapFactory.decodeStream(response.body().byteStream());
             } catch (IOException e) {
                 e.printStackTrace();
             }
             return bitmap;
         }

         @Override
         protected void onPostExecute(BitmapDrawable drawable) {
             super.onPostExecute(drawable);

             if ( mImageView != null && drawable != null){
                 mImageView.setImageDrawable(drawable);
             }
         }
     }
}

说明:Adapter中没什么好说的 就是一个简单的异步加载图片。

特殊说明:
1、我使用的是 Okhttp 连接网络 下载图片。
①、Eclipse 开发者需要 自己下载OkHttp 的Jar包
②、Android Studio 开发者需要 在App目录下的 build.gradle 的dependencies 添加: compile ‘com.squareup.okhttp:okhttp:2.0.0’
2、下面一段说明来自 :Hyman 的一篇博客
可以看到数据适配器与BaseAdapter比较发生了相当大的变化,主要有3个方法:
getItemCount 这个不用说,获取总的条目数

onCreateViewHolder 创建ViewHolder

onBindViewHolder 将数据绑定至ViewHolder

可见,RecyclerView对ViewHolder也进行了一定的封装,但是如果你仔细观察,你会发出一个疑问,ListView里面有个getView返回View为Item的布局,那么这个Item的样子在哪控制?

其实是这样的,我们创建的ViewHolder必须继承RecyclerView.ViewHolder,这个RecyclerView.ViewHolder的构造时必须传入一个View,这个View相当于我们ListView getView中的convertView (即:我们需要inflate的item布局需要传入)。

还有一点,ListView中convertView是复用的,在RecyclerView中,是把ViewHolder作为缓存的单位了,然后convertView作为ViewHolder的成员变量保持在ViewHolder中,也就是说,假设没有屏幕显示10个条目,则会创建10个ViewHolder缓存起来,每次复用的是ViewHolder,所以他把getView这个方法变为了onCreateViewHolder。有兴趣的自己打印下Log,测试下。

我们还需要图片s,不得不说的是我很懒。

package cn.com.zyh;

/**
 * Created by zyh on 2015/9/15.
 */
public class Images {

    public final static String []imageUrls={
            "https://img-my.csdn.net/uploads/201508/05/1438760758_3497.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760758_6667.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760757_3588.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760756_3304.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760755_6715.jpeg",
            "https://img-my.csdn.net/uploads/201508/05/1438760726_5120.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760726_8364.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760725_4031.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760724_9463.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760724_2371.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760707_4653.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760706_6864.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760706_9279.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760704_2341.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760500_6063.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760499_6304.jpeg",
            "https://img-my.csdn.net/uploads/201508/05/1438760499_5081.jpg",

            "https://img-my.csdn.net/uploads/201508/05/1438760445_3283.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760444_8623.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760444_6822.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760422_2224.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760421_2824.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760420_2660.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760420_7188.jpg",
            "https://img-my.csdn.net/uploads/201508/05/1438760419_4123.jpg",
    };
}

到这里我们的Demo写完了。
不要忘记在Manifest中添加访问网络的权限

<uses-permission android:name="android.permission.INTERNET"/>

可以测试啦!
测试结果:异步加载图片存在错乱现象。
这里写图片描述

  • 第二步:解决问题。
    话说能解决的问题都不是问题。md也不知道谁说的,不过挺有道理的。
    解决这个问题的思路和ListView一样,是在Adapter中做修改:
    具体代码如下:
package cn.com.zyh;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.support.v7.widget.RecyclerView;
import android.util.LruCache;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

import java.io.IOException;
import java.lang.ref.WeakReference;

/**
 * Created by zyh on 2015/9/15.
 */
public class MyRecyclerViewAdapter_Best extends RecyclerView.Adapter<MyRecyclerViewAdapter_Best.ViewHolder> {

    private String [] mImages;
    private Context mContext;
    private LayoutInflater mInflater;

    private Bitmap mBitmap; //

    private LruCache<String ,BitmapDrawable> mMemoryCache;//

    public MyRecyclerViewAdapter_Best(Context context,String [] imagesUrls){
        this.mContext= context;
        mImages = imagesUrls;
        mInflater = LayoutInflater.from(context);
        //默认显示的图片
        mBitmap = BitmapFactory.decodeResource(context.getResources(),R.drawable.default_image);
        //计算内存,并且给Lrucache 设置缓存大小
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        int cacheSize = maxMemory/6;
        mMemoryCache = new LruCache<String ,BitmapDrawable>(cacheSize){
            @Override
            protected int sizeOf(String key, BitmapDrawable value) {
                return  value.getBitmap().getByteCount();
            }
        };
    }

    @Override
    public MyRecyclerViewAdapter_Best.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = mInflater.inflate(R.layout.recycler_view,parent,false);
        ViewHolder holder = new ViewHolder(view);
        holder.imageView = (ImageView)view.findViewById(R.id.id_imageView);
        return holder;
    }

    @Override
    public void onBindViewHolder(MyRecyclerViewAdapter_Best.ViewHolder holder, int position) {

        String imageUrl= mImages[position];
        BitmapDrawable drawable = getBitmapDrawableFromMemoryCache(imageUrl);
        if (drawable != null){
            holder.imageView.setImageDrawable(drawable);
        }else if (cancelPotentialTask(imageUrl,holder.imageView)){
            //执行下载操作
            DownLoadTask task = new DownLoadTask(holder.imageView);
            AsyncDrawable asyncDrawable = new AsyncDrawable(mContext.getResources(),mBitmap,task);
            holder.imageView.setImageDrawable(asyncDrawable);
            task.execute(imageUrl);
        }
    }

    /**
     * 检查复用的ImageView中是否存在其他图片的下载任务,如果存在就取消并且返回ture 否则返回 false
     * @param imageUrl
     * @param imageView
     * @return
     */
    private boolean cancelPotentialTask(String imageUrl, ImageView imageView) {
        DownLoadTask task = getDownLoadTask(imageView);
        if (task != null) {
            String url = task.url;
            if (url == null || !url .equals(imageUrl)){
                task.cancel(true);
            }else{
                return false;
            }
        }
        return true;
    }


    /**
     * 從缓存中获取已存在的图片
     * @param imageUrl
     * @return
     */
    private BitmapDrawable getBitmapDrawableFromMemoryCache(String imageUrl) {
        return mMemoryCache.get(imageUrl);
    }

    /**
     * 添加图片到缓存中
     * @param imageUrl
     * @param drawable
     */
    private void addBitmapDrawableToMemoryCache(String imageUrl,BitmapDrawable drawable){
        if (getBitmapDrawableFromMemoryCache(imageUrl) == null ){
            mMemoryCache.put(imageUrl, drawable);
        }
    }

    /**
     * 获取当前ImageView 的图片下载任务
     * @param imageView
     * @return
     */
    private DownLoadTask getDownLoadTask(ImageView imageView){
        if (imageView != null){
            Drawable drawable  = imageView.getDrawable();
            if (drawable instanceof AsyncDrawable ){
                return  ((AsyncDrawable) drawable).getDownLoadTaskFromAsyncDrawable();
            }
        }
        return null;
    }


    @Override
    public int getItemCount() {  return mImages.length;  }

    public class ViewHolder extends RecyclerView.ViewHolder {
        ImageView imageView;
        public ViewHolder(View itemView) {
            super(itemView);
        }
    }

    /**
     * 新建一个类 继承BitmapDrawable
     * 目的: BitmapDrawable 和DownLoadTask建立弱引用关联
     */
    class AsyncDrawable extends  BitmapDrawable{
        private  WeakReference<DownLoadTask> downLoadTaskWeakReference;

        public AsyncDrawable(Resources resources,Bitmap bitmap,DownLoadTask downLoadTask){
            super(resources,bitmap);
            downLoadTaskWeakReference = new WeakReference<DownLoadTask>(downLoadTask);
        }

        private DownLoadTask getDownLoadTaskFromAsyncDrawable(){
          return downLoadTaskWeakReference.get();
        }
    }

    /**
     * 异步加载图片
     * DownLoadTash 和 ImagaeView建立弱引用关联。
     */
    class DownLoadTask extends AsyncTask<String ,Void,BitmapDrawable> {
        String url;
        private WeakReference<ImageView> imageViewWeakReference;
        public DownLoadTask(ImageView imageView){
            imageViewWeakReference = new WeakReference<ImageView>(imageView);
        }
        @Override
        protected BitmapDrawable doInBackground(String... params) {
            url = params[0];
            Bitmap bitmap = downLoadBitmap(url);
            BitmapDrawable drawable = new BitmapDrawable(mContext.getResources(),bitmap);
            addBitmapDrawableToMemoryCache(url,drawable);
            return  drawable;
        }

        /**
         * 验证ImageView 中的下载任务是否相同 如果相同就返回
         * @return
         */
        private ImageView getAttachedImageView() {
            ImageView imageView = imageViewWeakReference.get();
            if (imageView != null){
            DownLoadTask task = getDownLoadTask(imageView);
               if (this == task ){
                   return  imageView;
               }
           }
            return null;
        }

        /**
         * 下载图片 这里使用google 推荐使用的OkHttp
         * @param url
         * @return
         */
        private Bitmap downLoadBitmap(String url) {
            Bitmap bitmap = null;
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder().url(url).build();
            try {
                Response response = client.newCall(request).execute();
                bitmap = BitmapFactory.decodeStream(response.body().byteStream());
            } catch (IOException e) {
                e.printStackTrace();
            }
            return bitmap;
        }

        @Override
        protected void onPostExecute(BitmapDrawable drawable) {
            super.onPostExecute(drawable);
            ImageView imageView = getAttachedImageView();
            if ( imageView != null && drawable != null){
                imageView.setImageDrawable(drawable);
            }
        }


    }

}

写完了,求结果,效果图…..
这里写图片描述

效果图中的蓝色是我设置的默认背景图。

感谢前辈们的分享。

第一次这儿用心的写文章,还是有些乱。
源码下载

  • 4
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值