您的位置:首页 > 移动开发 > Android开发

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

2017-01-04 01:00 1806 查看
**************这个开源项目有点大的,也不知道几篇能写完,先根据功能点分析解读*********************

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<? extends ImageDecoder> 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以及采样率。

最后进行图片的绘
4000


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

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

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

这么写下来没个几篇是写不完了,这篇就到这,下一篇会分析双击屏幕后的处理。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐