SubsamplingScaleImageView可用于超大图片的显示,显示图片时有如下几个特点
- 在放大后只显示图片一部分时,使用官方提供的BitmapRegionDecoder类解码局部图片
- 根据当前缩放比选择合适的采样率进行采样,并根据当前可显示范围将解码后的图片拆分为多个tile存放在tilemap里,tile里存储着拆分后的其中一个bitmap
- 当缩放比发生改变时,判断当前tilemap里的tile是否还有用,如果无用就回收bitmap重新解析原图片然后拆分为多个tile
使用
使用起来很简单,在xml里
<com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
android:width="match_parent"
android:height="match_parent"
android:id="@+id/imageView"/>
在java代码里设置图片资源
//使用assets目录下的图片
view.setImage(ImageSource.asset("sanmartino.jpg"));
//使用res目录下的图片
imageView.setImage(ImageSource.resource(R.drawable.monkey));
//使用绝对路径
imageView.setImage(ImageSource.uri("/sdcard/DCIM/DSCM00123.JPG"));
图片加载流程
先来看看它是如何显示图片的,以 view.setImage(ImageSource.asset("sanmartino.jpg")); 为例(其他的类似)
设置基本参数
先看 ImageSource.asset("sanmartino.jpg")
public static ImageSource asset(@NonNull String assetName) {
//noinspection ConstantConditions
if (assetName == null) {
throw new NullPointerException("Asset name must not be null");
}
return uri(ASSET_SCHEME + assetName);
}
public static ImageSource uri(@NonNull String uri) {
//noinspection ConstantConditions
//省略无关代码
return new ImageSource(Uri.parse(uri));
}
private ImageSource(@NonNull Uri uri) {
// #114 If file doesn't exist, attempt to url decode the URI and try again
String uriString = uri.toString();
//省略无关代码
this.bitmap = null;
this.uri = uri;
this.resource = null;
this.tile = true;
}
可以看到最终返回了一个ImageSource,这个ImageSource里包含了图片的地址信息,并且默认开启tile(即会采用tilemap将图片拆分为多个包含局部bitmap的tile)
接下来看看setImage这个方法
public final void setImage(@NonNull ImageSource imageSource) {
setImage(imageSource, null, null);
}
public final void setImage(@NonNull ImageSource imageSource, ImageSource previewSource, ImageViewState state) {
//noinspection ConstantConditions
if (imageSource == null) {
throw new NullPointerException("imageSource must not be null");
}
//这个方法主要将各种参数(缩放,显示范围,matrix等)置为初始值(或默认值)
//并且这里传递了true,表示这是一张新的图片,所以还会将bitmap,tilemap,decoder等回收掉
reset(true);
if (state != null) { restoreState(state); }
if (previewSource != null) {
//省略,没用到预览图
}
if (imageSource.getBitmap() != null && imageSource.getSRegion() != null) {
//省略,从面可知这里bitmap为null
} else if (imageSource.getBitmap() != null) {
//省略,从面可知这里bitmap为null
} else {
//这里sRegion为null
sRegion = imageSource.getSRegion();
uri = imageSource.getUri();
if (uri == null && imageSource.getResource() != null) {
//省略,这里uri不为null
}
if (imageSource.getTile() || sRegion != null) {
// Load the bitmap using tile decoding.
//省略,这里sRegion为null
} else {
// Load the bitmap as a single image.
//bitmapDecoderFactory可以理解为一个用于获得图片解码器的工厂类
BitmapLoadTask task = new BitmapLoadTask(this, getContext(), bitmapDecoderFactory, uri, false);
execute(task);
}
}
}
Bitmap加载
接下来就是BitmapLoadTask,这是一个继承自AsyncTask的后台任务类
看看它的doInBackground方法
protected Integer doInBackground(Void... params) {
try {
String sourceUri = source.toString();
//这个类用虚引用来保存context,decoderFactory和view,防止内存泄漏
Context context = contextRef.get();
DecoderFactory<? extends ImageDecoder> decoderFactory = decoderFactoryRef.get();
SubsamplingScaleImageView view = viewRef.get();
if (context != null && decoderFactory != null && view != null) {
view.debug("BitmapLoadTask.doInBackground");
//decoderFactory.make()即使用该工厂获得一个解码器类
bitmap = decoderFactory.make().decode(context, source);
return view.getExifOrientation(context, sourceUri);
}
} catch (Exception e) {
Log.e(TAG, "Failed to load bitmap", e);
this.exception = e;
} catch (OutOfMemoryError e) {
Log.e(TAG, "Failed to load bitmap - OutOfMemoryError", e);
this.exception = new RuntimeException(e);
}
return null;
}
decoderFactory.make()会获得了一个解码器类SkiaImageDecoder,看看SkiaImageDecoder#decode方法
public Bitmap decode(Context context, @NonNull Uri uri) throws Exception {
String uriString = uri.toString();
BitmapFactory.Options options = new BitmapFactory.Options();
Bitmap bitmap;
options.inPreferredConfig = bitmapConfig;
if (uriString.startsWith(RESOURCE_PREFIX)) {
//如果使用的是res目录下的图片,省略
} else if (uriString.startsWith(ASSET_PREFIX)) {
String assetName = uriString.substring(ASSET_PREFIX.length());
bitmap = BitmapFactory.decodeStream(context.getAssets().open(assetName), null, options);
} else if (uriString.startsWith(FILE_PREFIX)) {
//如果使用的是手机目录下的图片,省略
} else {
//其他情况,省略
}
if (bitmap == null) {
//异常情况处理,省略
}
return bitmap;
}
继续看BitmapLoadTask#onPostExecute方法
protected void onPostExecute(Integer orientation) {
SubsamplingScaleImageView subsamplingScaleImageView = viewRef.get();
if (subsamplingScaleImageView != null) {
if (bitmap != null && orientation != null) {
if (preview) {
subsamplingScaleImageView.onPreviewLoaded(bitmap);
} else {
//bitmap成功加载
subsamplingScaleImageView.onImageLoaded(bitmap, orientation, false);
}
} else if (exception != null && subsamplingScaleImageView.onImageEventListener != null) {
//异常处理,省略
}
}
}
字数限制,下篇看这里 SubsamplingScaleImageView解析(下)