Bitmap(四)

  Bitmap(一)  http://www.cnblogs.com/fishbone-lsy/p/4486571.htmlhtml

  Bitmap(二)    http://www.cnblogs.com/fishbone-lsy/p/4496166.htmljava

  Bitmap(三)    http://www.cnblogs.com/fishbone-lsy/p/4526922.htmlandroid

  在Bitmap(一)中介紹了,Bitmap如何經過BitmapFactory得到圖片資源。在Bitmap(二)中,介紹了將Bitmap進行預加載,縮放,再載入ImageView。Bitmap(三)中,介紹了經過異步任務加載圖片,並使用弱引用封裝ImageView。app

  上面三節在加載單張圖片時,已經知足基本要求了。可是,在多個圖片的ListView中,那個方法就不適用了。異步

  上一節最後的代碼是這樣的async

        LoadImageTask task = new LoadImageTask(imageView1 , 100 , 100);
        task.execute(R.drawable.images);

  若是我將上面一段代碼放在自定義Adapter裏面的getView中,每個Item都會開啓一個新的線程。因爲Item在getView中會以viewHolder的形式重用,當用戶瘋狂地上下滑動ListView時,就會產生N個LoadImageTask在爲同一個ImageView工做。這顯然是很是不科學的。ide

  咱們但願,每一個ImageView只有一個屬於本身的線程,若是這個ImageView被重用有了新的任務,那麼它前面未加載完成的任務也應該停止掉。所以在這一節中,咱們試圖將ImageView和LoadImageTask綁定起來,順便給它一個defaultBitmap用做加載過程當中的顯示圖片。它的最終形式應該是這個樣子this

 public void loadBitmap(int resId, ImageView imageView , int defaultResId , int reqWidth , int reqHeight)

  使用它時,應該只須要這樣子spa

loadBitmap(R.drawable.images2 , imageView1 , R.drawable.images , 100 , 100);

 

  好了,開始說怎麼作。第一步,就如上面所說,咱們要綁定ImageView和LoadImageTask,順便加上defaultBitmap。因此首先使用了BitmapDrawable線程

    /**
     * 將Bitmap的加載task封裝進BitmapDrawable中,設置默認圖,並在加載完新圖後換掉
     */
    private class AsyncDrawable extends BitmapDrawable{
        private final WeakReference<LoadImageTask> bitmapWorkTask ;
        public AsyncDrawable(Resources resources, Bitmap bitmap, LoadImageTask bitmapWorkTask){
            super(resources,bitmap);
            this.bitmapWorkTask = new WeakReference<LoadImageTask>(bitmapWorkTask);
        }
        public LoadImageTask getLoadImageTask(){
            return bitmapWorkTask.get();
        }
    }

 

在上面的代碼中,經過一個弱引用,將LoadImageTask和Bitmap綁定了起來。

  而後,將封裝了Bitmap和LoadImageTask的AsyncDrawable,經過imageView.setImageDrawable(asyncDrawable);直接賦給ImageView,最後再開始加載異步任務。

    /**
     * 加載圖片
     * @param resId
     * @param imageView
     * @param defaultResId
     * @param reqWidth
     * @param reqHeight
     */
    public void loadBitmap(int resId, ImageView imageView , int defaultResId , int reqWidth , int reqHeight) {
        //判斷任務是否正在進行
        if (cancelPotentialWork(resId, imageView)) {
            final LoadImageTask task = new LoadImageTask(imageView , reqWidth , reqHeight );
            AsyncDrawable asyncDrawable = null;
            try{
                asyncDrawable = new AsyncDrawable(getResources(), BitmapFactory.decodeResource(getResources() , defaultResId), task);
            }catch (Exception ex){
                asyncDrawable = new AsyncDrawable(getResources(), BitmapFactory.decodeResource(getResources() , R.mipmap.ic_launcher) , task);
            }
            imageView.setImageDrawable(asyncDrawable);
            task.execute(resId);
        }
    }

   這樣,每次賦給ImageView的不單單是一張圖片,而是一張默認圖片、一張目標圖片和一個加載目標圖片的異步任務。當咱們要在ImageView上開一個新任務時,能夠在新開任務前判斷,我要開始的任務的目標是否是和正在進行的任務目標一致,若是一致就不要新開任務了。

  那麼如何進行這樣的判斷呢?

  首先,咱們須要一個方法,能取出ImageView中的LoadImageTask,原理是取出ImageView中的Drawable,而後判斷它是否是AsyncDrawable,進而取出它的LoadImageTask。

    /**
     * 得到視圖當前任務
     * @param imageView
     * @return
     */
    private LoadImageTask getLoadImageTask(ImageView imageView){
        if(imageView != null ){
            final Drawable drawable = imageView.getDrawable();
            if(drawable instanceof AsyncDrawable){
                final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
                return asyncDrawable.getLoadImageTask();
            }
        }
        return null;
    }

 

  而後,咱們須要給異步任務類加入可以取出它當前正在進行的任務的目標的方法

    /**
     * 異步加載圖像的任務類
     */
    private class LoadImageTask extends AsyncTask<Integer , Void , Bitmap>{
        private final  WeakReference imageViewReference;
        private final int mReqWidth , mReqHeight;
        private int data = 0;
        public LoadImageTask(ImageView imageView , int reqWidth , int reqHeight){
            imageViewReference = new WeakReference<ImageView>(imageView);
            mReqWidth = reqWidth;
            mReqHeight = reqHeight;
        }
        @Override
        protected Bitmap doInBackground(Integer... params) {
            data = params[0];
            return decodeSampledBitmapFromResource(getResources() , params[0],mReqWidth , mReqHeight);
        }
        @Override
        protected void onPostExecute(Bitmap bitmap) {
            //由於目標切換等緣由停止了任務
            if (isCancelled()) { bitmap = null; } if(bitmap!=null && imageViewReference !=null){
                final ImageView imageView = (ImageView) imageViewReference.get();
                if(imageView != null){

                    final LoadImageTask loadImageTask = getLoadImageTask(imageView);
                    if (this == loadImageTask){
                        imageView.setImageBitmap(bitmap);
                    }
                }
            }
        }
        public int getData() { return data; }
    }

值得一提的是,所謂的停止異步任務,並非說可以在異步任務外的UI線程中,停止它。咱們所能作的,只能是將異步任務中的cancel標誌位設爲true,真正停止它的,只能是在異步任務內,經過檢測isCancelled來決定它是否要使本身無效。

  最後,是判斷目標任務與正在進行中的任務是否一致的方法:

    /**
     * 經過判斷任務中的數據,任務是否已經開始,或者任務目標與當前需求目標不一樣。
     * @param res
     * @param imageView
     * bitmapData爲正在進行中的任務的目標resId
     * @return false:目標任務與正在進行的任務相同
     */
    private boolean cancelPotentialWork(int res , ImageView imageView){
        final LoadImageTask loadImageTask = getLoadImageTask(imageView);
        if (loadImageTask!=null){
            final int bitmapData = loadImageTask.getData();
            // If bitmapData is not yet set or it differs from the new data
            if (bitmapData == 0  ||  bitmapData != res ){
                loadImageTask.cancel(true);
            }
            else{
                return false;
            }
        }
        return true;
    }

該方法只有在沒有進行中的任務,或者目標任務與正在進行中的任務不一樣時,纔會返回true。而後結合loadBitmap方法,只有在返回true時,纔會加載圖片,開啓新任務。

  

  雖然過程比較繁瑣,可是用起來,就簡單了!

    public void createImage(View view){
        loadBitmap(R.drawable.images2 , imageView1 , R.drawable.images , 30 , 30);
    }

  Done

 

最後附上一套稍微完整一點的代碼

import android.app.Activity;
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.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;

import java.lang.ref.WeakReference;


public class MainActivity extends Activity {
    private ImageView imageView1,imageView2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView1 = (ImageView) findViewById(R.id.image1);
    }

    /**
     * 計算圖象的縮放比例
     * @param options   圖像的參數設置接口
     * @param reqWidth  要求的寬
     * @param reqHeight 要求的高
     * @return  縮放比例,係數越大縮小的越多
     */
    private int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) > reqHeight
                    && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }

    /**
     * 壓縮圖片
     * @param res           系統資源
     * @param resId         壓縮前的資源ID
     * @param reqWidth      要求的寬
     * @param reqHeight     要求的高
     * @return  壓縮後的Bitmap圖
     */
    private Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
                                                         int reqWidth, int reqHeight) {

        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }

    /**
     * 異步加載圖像的任務類
     */
    private class LoadImageTask extends AsyncTask<Integer , Void , Bitmap>{
        private final  WeakReference imageViewReference;
        private final int mReqWidth , mReqHeight;
        private int data = 0;
        public LoadImageTask(ImageView imageView , int reqWidth , int reqHeight){
            imageViewReference = new WeakReference<ImageView>(imageView);
            mReqWidth = reqWidth;
            mReqHeight = reqHeight;
        }
        @Override
        protected Bitmap doInBackground(Integer... params) {
            data = params[0];
            return decodeSampledBitmapFromResource(getResources() , params[0],mReqWidth , mReqHeight);
        }
        @Override
        protected void onPostExecute(Bitmap bitmap) {
            //由於目標切換等緣由停止了任務
            if (isCancelled()) {
                bitmap = null;
            }
            if(bitmap!=null && imageViewReference !=null){
                final ImageView imageView = (ImageView) imageViewReference.get();
                if(imageView != null){

                    final LoadImageTask loadImageTask = getLoadImageTask(imageView);
                    if (this == loadImageTask){
                        imageView.setImageBitmap(bitmap);
                    }

                }
            }
        }
        public int getData() {
            return data;
        }
    }

    /**
     * 將Bitmap的加載task封裝進BitmapDrawable中,設置默認圖,並在加載完新圖後換掉
     */
    private class AsyncDrawable extends BitmapDrawable{
        private final WeakReference<LoadImageTask> bitmapWorkTask ;
        public AsyncDrawable(Resources resources, Bitmap bitmap, LoadImageTask bitmapWorkTask){
            super(resources,bitmap);
            this.bitmapWorkTask = new WeakReference<LoadImageTask>(bitmapWorkTask);
        }
        public LoadImageTask getLoadImageTask(){
            return bitmapWorkTask.get();
        }
    }

    /**
     * 加載圖片
     * @param resId
     * @param imageView
     * @param defaultResId
     * @param reqWidth
     * @param reqHeight
     */
    public void loadBitmap(int resId, ImageView imageView , int defaultResId , int reqWidth , int reqHeight) {
        //判斷任務是否正在進行
        if (cancelPotentialWork(resId, imageView)) {
            final LoadImageTask task = new LoadImageTask(imageView , reqWidth , reqHeight );
            AsyncDrawable asyncDrawable = null;
            try{
                asyncDrawable = new AsyncDrawable(getResources(), BitmapFactory.decodeResource(getResources() , defaultResId), task);
            }catch (Exception ex){
                asyncDrawable = new AsyncDrawable(getResources(), BitmapFactory.decodeResource(getResources() , R.mipmap.ic_launcher) , task);
            }
            imageView.setImageDrawable(asyncDrawable);
            task.execute(resId);
        }
    }

    /**
     * 經過判斷任務中的數據,任務是否已經開始,或者任務目標與當前需求目標不一樣。
     * @param res
     * @param imageView
     * bitmapData爲正在進行中的任務的目標resId
     * @return false:目標任務與正在進行的任務相同
     */
    private  boolean cancelPotentialWork(int res , ImageView imageView){
        final LoadImageTask loadImageTask = getLoadImageTask(imageView);
        if (loadImageTask!=null){
            final int bitmapData = loadImageTask.getData();
            // If bitmapData is not yet set or it differs from the new data
            if (bitmapData == 0  ||  bitmapData != res ){
                loadImageTask.cancel(true);
            }
            else{
                return false;
            }
        }
        return true;
    }

    /**
     * 得到視圖當前任務
     * @param imageView
     * @return
     */
    private  LoadImageTask getLoadImageTask(ImageView imageView){
        if(imageView != null ){
            final Drawable drawable = imageView.getDrawable();
            if(drawable instanceof AsyncDrawable){
                final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
                return asyncDrawable.getLoadImageTask();
            }
        }
        return null;
    }


    public void createImage(View view){
        loadBitmap(R.drawable.images2 , imageView1 , R.drawable.images , 30 , 30);
    }
}
相關文章
相關標籤/搜索