Android大图加载,缩放,滑动浏览--SubsamplingScaleImageView 源码分析大图加载

**************这个开源项目有点大的,也不知道几篇能写完,先根据功能点分析解读*********************

1.写在前面

图片浏览的坑不少,大图加载导致内存溢出的情况相信每个人都遇到过,最早的解决办法是利用 BitmapFactory.Options自己解决,简单的实现方式:

 public Bitmap decodeBitMapFromFileDescriptor(FileDescriptor fd,int reqWidth,int reqHeight){
        BitmapFactory.Options options = new BitmapFactory.Options();
        //解析目标图片宽高
        options.inJustDecodeBounds = true;

        BitmapFactory.decodeFileDescriptor(fd,null,options);

        options.inSampleSize = calculateInSimpleSize(options, reqWidth, reqHeight);

        options.inJustDecodeBounds = false;

        return BitmapFactory.decodeFileDescriptor(fd,null,options);
    }
    /**
     * 计算采样率
     * @param options
     * @param reqWidth
     *  @param reqHeight
     */
    private int calculateInSimpleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        //初始采样率
        int inSimpleSize = 1;
        //判0
        if(reqWidth==0||reqHeight==0){
            return 1;
        }
        //获取图片解析宽高
        final int width = options.outWidth;
        final int height = options.outHeight;
        LogUtil.LogE("原始图片宽高,width:"+width+",heght:"+height);
        if(height>reqHeight||width>reqWidth){
            //任意宽高大于需求宽高
            final int halfHeight = height/2;
            final int halfWidth = width/2;
            //定义循环 不断缩小halfheight 直到任意小于目标宽高跳出循环
            while((halfHeight/inSimpleSize)>=reqHeight&&(halfWidth/inSimpleSize)>=reqWidth){
                //官方建议取值为2的指数幂
                inSimpleSize*=2;
            }
        }
        return inSimpleSize;
    }
这是根据《安卓开发艺术探索》 图片加载一章实现的图片缓存工具,这个类就是用来等比例缩小图片质量,从而减小bitmap占用内存,防止内存溢出。

但是实际需求中,当你一个页面显示大图浏览时,不可能是只显示图片就完了的,双击缩放,放大时滑动浏览,这些基本功能肯定要有,最早实现这些功能,是看了鸿洋大神的文章,模仿写的,博客地址:点击打开链接 ,勉强能用,后来发现了这个开源项目 github:https://github.com/davemorrissey/subsampling-scale-image-view,基本上你能想到的功能他都有了。下面我会根据几个功能点来解读源码。

二、源码分析

首先看功能,第一个肯定是最基本的大图加载,先说下使用方式:

public class MainActivity extends Activity {  
  
    private SubsamplingScaleImageView mSubsamplingScaleImageView;  
      
    @Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
          
        mSubsamplingScaleImageView=(SubsamplingScaleImageView) findViewById(R.id.subsamplingScaleImageView);  
        mSubsamplingScaleImageView.setImage(ImageSource.asset("china.jpg"));  
    }  
} 

ImageSource 类 指定图片加载途径,1、直接加载Bitmap ,2.、图片缓存路径加载、3.资源id加载、4.asset 资源文件加载(超大图片必备);与此同时他还初始化了一些bitmap的特性值,最重要的就是 region这是一个 Rect指定了显示大图某一个区域,详细的介绍后面会说。先来看setImage以后做了什么

if (imageSource.getBitmap() != null && imageSource.getSRegion() != null) {
            //从bitmap加载 指定显示区域
            onImageLoaded(Bitmap.createBitmap(imageSource.getBitmap(), imageSource.getSRegion().left, imageSource.getSRegion().top, imageSource.getSRegion().width(), imageSource.getSRegion().height()), ORIENTATION_0, false);
        } else if (imageSource.getBitmap() != null) {
            //从bitmap加载 没有指定显示区域
            onImageLoaded(imageSource.getBitmap(), ORIENTATION_0, imageSource.isCached());
        } else {
            // imageSource.getBitmap() ==null 从resource asset 加载图片
            sRegion = imageSource.getSRegion();
            uri = imageSource.getUri();
            if (uri == null && imageSource.getResource() != null) {
                // 图片源是资源id
                uri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + getContext().getPackageName() + "/" + imageSource.getResource());
            }
            if (imageSource.getTile() || sRegion != null) {
                // Load the bitmap using tile decoding. 
                // 展开  
                TilesInitTask task = new TilesInitTask(this, getContext(), regionDecoderFactory, uri);
                execute(task);
            } else {
                // Load the bitmap as a single image.
                BitmapLoadTask task = new BitmapLoadTask(this, getContext(), bitmapDecoderFactory, uri, false);
                execute(task);
            }
        }

这里对图片源做了一系列判断,是否有Bitmap 对象,是否指定区域显示,关于图片的区域显示,也可先看看鸿洋大神的这篇文章:http://blog.csdn.net/lmj623565791/article/details/49300989 

这里首先进入TilesInitTask 进行展开初始化任务,获取图片宽高和方向信息,进行第一次绘制,从onDraw中进入initialiseBaseLayer方法

这个Task的主要目的就是判断需要绘制的bitmap宽高有没有超出Canvas的最大绘制宽高,防止报错;如果没有超出,就从BitmapLoadTask进行正常加载图片。

核心类就是 BitmapRegionDecoder  前面说的region就是通过它来显示指定一个矩形区域; 进入BitmapLoadTask ,AsyncTask子类,核心处理在doInbackgroud

 try {
                String sourceUri = source.toString();
                Context context = contextRef.get();
                //根据实现类不同 decoderFactory的解析方式不同 缩放模式显示指定区域时,
                // 实现类factory初始化了BitmapRegionDecoder 对象,从而指定region 解析出bitmap (可以自己看下SkiaImageRegionDecoder这个类源码)
                //这里factory实现类 通过BitmapFactory来解析bitmap 
                DecoderFactory decoderFactory = decoderFactoryRef.get();
                SubsamplingScaleImageView view = viewRef.get();
                if (context != null && decoderFactory != null && view != null) {
                    view.debug("BitmapLoadTask.doInBackground");
                    // 没有指定显示区域时解析获取bitmap对象
                    bitmap = decoderFactory.make().decode(context, source);
                    //返回值是图片方向
                    return view.getExifOrientation(context, sourceUri);
                }
            } ...错误处理

同样的对BitmapRegionDecoder解析的bitmap对象进行全尺寸的图片加载 在加载方法中对当前图片的属性值进行初始化,宽高,方向等,然后再次进入onDraw方法,在fitBounds方法中计算图片scale以及采样率。

最后进行图片的绘制

else if (bitmap != null) {

            float xScale = scale, yScale = scale;
            if (matrix == null) { matrix = new Matrix(); }
            matrix.reset();
            matrix.postScale(xScale, yScale);
            matrix.postRotate(getRequiredRotation());
            matrix.postTranslate(vTranslate.x, vTranslate.y);

            if (getRequiredRotation() == ORIENTATION_180) {
                matrix.postTranslate(scale * sWidth, scale * sHeight);
            } else if (getRequiredRotation() == ORIENTATION_90) {
                matrix.postTranslate(scale * sHeight, 0);
            } else if (getRequiredRotation() == ORIENTATION_270) {
                matrix.postTranslate(0, scale * sWidth);
            }

            if (tileBgPaint != null) {
                if (sRect == null) { sRect = new RectF(); }
                sRect.set(0f, 0f, bitmapIsPreview ? bitmap.getWidth() : sWidth, bitmapIsPreview ? bitmap.getHeight() : sHeight);
                matrix.mapRect(sRect);
                canvas.drawRect(sRect, tileBgPaint);
            }
            canvas.drawBitmap(bitmap, matrix, bitmapPaint);

        }

这里需要知道是的Matrix,这是一个三维矩阵,内部存储了一个长度为9 的数组

{  
        MSCALE_X, MSKEW_X, MTRANS_X,    
        MSKEW_Y, MSCALE_Y, MTRANS_Y,    
        MPERSP_0, MPERSP_1, MPERSP_2    
};  
这几个值控制了图像的位移,缩放,旋转,具体使用时有直接方法,可以去看Api

到这里就完成一次完成大图的缩放加载。与之前的方法不同的是,这样加载后并没有损失图像质量,便于放大后查看,文章开始的方法使用采样率加载通过损失图像质量来降低内存小号,这里则是缩放图像来降低内存。


--------------------------------------------------------------

这么写下来没个几篇是写不完了,这篇就到这,下一篇会分析双击屏幕后的处理。





你可能感兴趣的:(源码解析)