仿微信倒序加载图片

实现效果


这里写图片描述
这个一个仿微信倒序加载图片的照片墙,虽然不怎么难,但是牵扯到的技术点还是蛮多的,有必要一起学习下,总结下,这样以后妈妈在也不担心Android图片加载了。

 

网络请求和压缩


这里为了方便使用了volley,volley虽然是google官方推荐的网络加载库,但是对于图片加载的效果还是不错的,当然重量级的还是使用glide框架,这是google推荐的图片加载库。volley就不用多说了,郭神的博客一大把,可以去看看。来看看图片压缩:

    public Bitmap compressSizeFromBmp(Bitmap image,int desWidth,int desHeight,Context context){
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int options = 100;
        image.compress(Bitmap.CompressFormat.JPEG, 30, baos);

        // * 特点: 通过设置采样率, 减少图片的像素, 达到对内存中的Bitmap进行压缩
        while (baos.toByteArray().length / 1024 > 100) { 
            baos.reset();
            options -= 10;
            image.compress(Bitmap.CompressFormat.JPEG, options, baos);
        }

        //      ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
        //      Bitmap bitmap  = BitmapFactory.decodeStream(isBm, null, null);

        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        newOpts.inJustDecodeBounds = true;//只读边,不读内容
        //      Bitmap bitmap = BitmapFactory.decodeFile(srcPath, newOpts);
        Bitmap bm = BitmapFactory.decodeByteArray(baos.toByteArray(), 0, baos.size(), newOpts);

        if(!image.isRecycled()){
            image.recycle();    
        }

        //下次读内容,压缩大小
        newOpts.inJustDecodeBounds = false;
        int w = newOpts.outWidth;
        int h = newOpts.outHeight;
        float hh = dip2px(context, desWidth);
        float ww = dip2px(context, desHeight);

        int be = 1;
        if (w > h && w > ww) {
            be = (int) (newOpts.outWidth / ww);
        } else if (w < h && h > hh) {
            be = (int) (newOpts.outHeight / hh);
        }
        if (be <= 0)
            be = 1;

        newOpts.inSampleSize = be;//设置采样率

        newOpts.inPreferredConfig = Config.ARGB_8888;//该模式是默认的,可不设
        newOpts.inPurgeable = true;// 同时设置才会有效
        newOpts.inInputShareable = true;//。当系统内存不够时候图片自动被回收

        bm = BitmapFactory.decodeByteArray(baos.toByteArray(), 0, baos.size(), newOpts);

        return bm;
    }

这里使用了对图片的像素和大小分别进行了压缩,代码也很常用,值得注意的是,讲inJustDecodeBounds 属性设置为false,则是读取图片的边界信息,是一个轻量级的操作。

双缓存


当构建volley的ImageLoader的时候:

mQueue = Volley.newRequestQueue(this);
        myImageCache = new MyImageCache();
        imageLoader = new ImageLoader(mQueue, myImageCache);

要指定一个图片缓存类,volley指定了使用L1 cache:

   /**
     * Constructs a new ImageLoader.
     * @param queue The RequestQueue to use for making image requests.
     * @param imageCache The cache to use as an L1 cache.
     */
    public ImageLoader(RequestQueue queue, ImageCache imageCache) {
        mRequestQueue = queue;
        mCache = imageCache;
    }

我们这里用双缓存,就是内存和硬盘缓存,用的都是Lru算法,即近期最少使用算法。这些都是很常见的东西,这里就不多赘述。

    class MyImageCache implements ImageCache{
        AsyncImageLoader asyncImageLoader= AsyncImageLoader.getInstance(MainActivity.this);
        public Bitmap getBitmap(String url) {
            Bitmap bitmap = null;
            try {
                bitmap = asyncImageLoader.getBitmapFromMem(url);
                if(bitmap == null){
                    bitmap = asyncImageLoader.getBitmapFromDisk(url);
                }
            } catch (Exception e) {
                Log.e("MyImageCache", "can't get bitmap from MyImageCache!");
            }
            return bitmap;
        }

        public void putBitmap(String url, Bitmap bitmap) {
            //对图片进行压缩
            bitmap = ImageUtil.compressBmpFromBmp(bitmap);
            //put image into disk and memory.
            asyncImageLoader.putBitmapinDisk(bitmap, url);
        }

        public void clearAllBitmap(){
            //只要删除内存中的图片资源就行了
            asyncImageLoader.clearBitmapInMemory();
            asyncImageLoader.clearContext();
        }
    }

MyImageCache所做的事情就是,首先将网络获取到的图片进行压缩,然后分别放入硬盘和内存,取得时候也是,先从内存取,取不到再从硬盘取,再取不到就重新加载…而AsyncImageLoader类则对LruCache和DiskLruCache进行了一个封装。详细见demo。

异步倒序加载


下面来实现这个需求,首先,大量的图片加载首先要解决的OOM的问题,我们前面几步都是为了它再做准备,其次,解决的是用户体验问题,比如说用户快速滑动的时候是否会出现卡顿现象。那么这个demo,是对这方便做了很多的工作,比如,当用户快速滑动的时候,我们不加载图片,等到停止的时候,我们在去加载,而且只加载显示在用户面前的图片。那么如何做实现,先贴代码:

public MyAdapter() {
            // TODO Auto-generated constructor stub
            inflater = MainActivity.this.getLayoutInflater();

            mGridView.setOnScrollListener(new OnScrollListener() {

                @Override
                public void onScrollStateChanged(AbsListView view, int scrollState) {
                    // TODO Auto-generated method stub
                    switch (scrollState) {
                    case AbsListView.OnScrollListener.SCROLL_STATE_FLING:
                        isTouch = false;
                        isFlying = true;
                        break;
                    case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
                        isTouch = false;
                        isFlying = false;
                        if (adapter != null) {
                            adapter.notifyDataSetChanged();
                        }
                        break;
                    case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
                        isFlying = false;
                        isTouch = true;
                        break;
                }
                }

                @Override
                public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                    // TODO Auto-generated method stub

                }
            });

            if(null == workThread){
                workThread = new Thread(){
                    public void run() {
                        while(isLoad){

                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }

                            if(! vector.isEmpty() ){
                                String url = vector.remove(vector.size()-1);
                                mHandler.sendMessage(mHandler.obtainMessage(UPDATE_IMAGE, url));
                            }

                            else{
                                try {
                                    synchronized (workThread) {
                                        workThread.wait();
                                    }
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    }
                };
                workThread.start();
            }

这个是GridView的Adapter的构造器,这里面我们多做了两步工作,第一个是监听了GridView的滚动事件,需知GridView是否是flying的状态,如果是就不加载,第二个是开启了一个线程,那么这个线程的作用是,控制加载图片的任务。

    iv.setTag(Images.imageThumbUrls[position]);
            ImageContainer image = ImageMaps.get(Images.imageThumbUrls[position]);
            if(image != null){
                if(image.getBitmap() != null){
                    iv.setImageBitmap(image.getBitmap());
                }
            }else{
                if(!isFlying){
                    vector.add(Images.imageThumbUrls[position]);
                    synchronized (workThread) {
                        workThread.notify();
                    }
                }
            }

在Adapter的getView操作中,我们给vector添加了加载图片的任务,如果正常滚动或者静止状态下,我们都是去发送异步加载图片的指令的。并且打开workThread的堵塞状态,让其工作。那么如果是飞快的滑动了呢,那么静止下来,怎么去加载图片呢,可以看到我们在监听器里面调用了adapter.notifydataSetChange的方法。接下来就是加载了:

    private static class MyHandler extends Handler{

        private final WeakReference<MainActivity> mActivity;

        public MyHandler(MainActivity activity) {
            mActivity = new WeakReference<MainActivity>(activity);
        }

        public void handleMessage(Message msg) {
            if(msg.what == UPDATE_IMAGE){
                try {
                    String url = (String) msg.obj;
                    SquareImageView iv = (SquareImageView ) mActivity.get().mGridView.findViewWithTag(url);

                    /**
                     * 在做一层缓存,让已经加载过的ImageView就不要重复请求网络了
                     */
                    boolean isHasUrl = false;
                    Iterator<String> it = mActivity.get().ImageMaps.keySet().iterator();
                    while(it.hasNext()){
                        if(url.equals(it.next()))
                        {
                            isHasUrl = true;
                            break;
                        }
                    }

                    if(!isHasUrl)
                    {
                        mActivity.get().ImageMaps.put(url,
                                mActivity.get().imageLoader.get(
                                url,
                                ImageLoader.getImageListener(iv, R.drawable.aio_image_default, R.drawable.aio_image_fail))
                                );
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

这里就是调用了ImageLoader的方法去加载图片,加载成功后,将图片放到做好标记的imageview上面就Ok了,那么倒序是如何实现的呢,就是每次加载Vector的最后一个任务,这样就实现了倒序的效果,让线程堵塞(休眠)1秒钟的原因就是有这样的一个效果,不然,加载太快了,还是看不出来。另外我们还可以做成,随机加载,都是很容易实现的。

源码下载

仿微信倒序加载图片

 

posted @ 2016-02-26 10:57  Nipuream  阅读(467)  评论(0编辑  收藏  举报