接上篇 SubsamplingScaleImageView解析(上)
Bitmap加载
接下来到onImageLoaded这个方法
private synchronized void onImageLoaded(Bitmap bitmap, int sOrientation, boolean bitmapIsCached) {
//处理边界情况,省略
//获取bitmap的各种信息
this.bitmapIsPreview = false;
this.bitmapIsCached = bitmapIsCached;
this.bitmap = bitmap;
this.sWidth = bitmap.getWidth();
this.sHeight = bitmap.getHeight();
this.sOrientation = sOrientation;
//下面这两行主要是在将图片对齐中心,更新tilemap
boolean ready = checkReady();
boolean imageLoaded = checkImageLoaded();
if (ready || imageLoaded) {
//重绘,调整view的大小
invalidate();
requestLayout();
}
}
看看SubsamplingScaleImageView#onMeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
boolean resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
boolean resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;
int width = parentWidth;
int height = parentHeight;
if (sWidth > 0 && sHeight > 0) {
if (resizeWidth && resizeHeight) {
//可以看到,一般情况下会把bitmap的长宽当做view的长宽
width = sWidth();
height = sHeight();
} else if (resizeHeight) {
height = (int)((((double)sHeight()/(double)sWidth()) * width));
} else if (resizeWidth) {
width = (int)((((double)sWidth()/(double)sHeight()) * height));
}
}
width = Math.max(width, getSuggestedMinimumWidth());
height = Math.max(height, getSuggestedMinimumHeight());
setMeasuredDimension(width, height);
}
接下来是SubsamplingScaleImageView#onDraw方法,很长,我适当做了删减
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
createPaints();
//处理边界情况,省略
// Set scale and translate before draw.
//这个之前在checkReady方法里已经调用过
preDraw();
// If animating scale, calculate current scale and center with easing equations
if (anim != null && anim.vFocusStart != null) {
//处理动画,省略
}
//重点!绘制图案
if (tileMap != null && isBaseLayerReady()) {
// Optimum sample size for current scale
int sampleSize = Math.min(fullImageSampleSize, calculateInSampleSize(scale));
// First check for missing tiles - if there are any we need the base layer underneath to avoid gaps
//如果当前有些tile可见但是bitmap还在加载中(或未加载),则置为true
boolean hasMissingTiles = false;
for (Map.Entry<Integer, List<Tile>> tileMapEntry : tileMap.entrySet()) {
if (tileMapEntry.getKey() == sampleSize) {
for (Tile tile : tileMapEntry.getValue()) {
if (tile.visible && (tile.loading || tile.bitmap == null)) {
hasMissingTiles = true;
}
}
}
}
// Render all loaded tiles. LinkedHashMap used for bottom up rendering - lower res tiles underneath.
for (Map.Entry<Integer, List<Tile>> tileMapEntry : tileMap.entrySet()) {
//找到当前采样率下的tilemap
if (tileMapEntry.getKey() == sampleSize || hasMissingTiles) {
for (Tile tile : tileMapEntry.getValue()) {
//sRect存储tile里的bitmap在原图像里所显示的矩形区域
//vRect由sRect根据当前缩放和平移计算后的矩形区域
sourceToViewRect(tile.sRect, tile.vRect);
if (!tile.loading && tile.bitmap != null) {
if (tileBgPaint != null) {
canvas.drawRect(tile.vRect, tileBgPaint);
}
if (matrix == null) { matrix = new Matrix(); }
matrix.reset();
setMatrixArray(srcArray, 0, 0, tile.bitmap.getWidth(), 0, tile.bitmap.getWidth(), tile.bitmap.getHeight(), 0, tile.bitmap.getHeight());
//判断显示的方向
if (getRequiredRotation() == ORIENTATION_0) {
setMatrixArray(dstArray, tile.vRect.left, tile.vRect.top, tile.vRect.right, tile.vRect.top, tile.vRect.right, tile.vRect.bottom, tile.vRect.left, tile.vRect.bottom);
} else if (getRequiredRotation() == ORIENTATION_90) {
setMatrixArray(dstArray, tile.vRect.right, tile.vRect.top, tile.vRect.right, tile.vRect.bottom, tile.vRect.left, tile.vRect.bottom, tile.vRect.left, tile.vRect.top);
} else if (getRequiredRotation() == ORIENTATION_180) {
setMatrixArray(dstArray, tile.vRect.right, tile.vRect.bottom, tile.vRect.left, tile.vRect.bottom, tile.vRect.left, tile.vRect.top, tile.vRect.right, tile.vRect.top);
} else if (getRequiredRotation() == ORIENTATION_270) {
setMatrixArray(dstArray, tile.vRect.left, tile.vRect.bottom, tile.vRect.left, tile.vRect.top, tile.vRect.right, tile.vRect.top, tile.vRect.right, tile.vRect.bottom);
}
//计算bitmap通过缩放和平移变成vRect的大小需要的变换矩阵,保存在matrix里
matrix.setPolyToPoly(srcArray, 0, dstArray, 0, 4);
canvas.drawBitmap(tile.bitmap, matrix, bitmapPaint);
if (debug) {
//debug信息,省略
}
} else if (tile.loading && debug) {
//debug下显示的图案,省略
}
if (tile.visible && debug) {
//debug下显示的图案,省略
}
}
}
}
} else if (bitmap != null && !bitmap.isRecycled()) {
//如果没有开启tile(即不使用tilemap),会来到这里,省略
}
if (debug) {
//画各种便于debug的图案,省略
}
}
TileMap更新
tilemap保存着各种采样率下的tilemapEntry,每个tilemapEntry保存着对应采样率下的tile,而每个tile里就保存着图片局部的bitmap
titlemap更新主要做以下事情
- 若当前采样率不等于tilemapEntry的采样率,就会将该titlemapEntry下的tile里的bitmap进行回收
- 若等于则会加载该采样率下的tile并设置tile里的bitmap(如果该采样率下的tile里的bitmap为null的话)
titlemap更新的时机
- 移动事件时
- 缩放事件完成之后
tilemap在SubsamplingScaleImageView#refreshRequiredTiles方法里
private void refreshRequiredTiles(boolean load) {
if (decoder == null || tileMap == null) { return; }
int sampleSize = Math.min(fullImageSampleSize, calculateInSampleSize(scale));
// Load tiles of the correct sample size that are on screen. Discard tiles off screen, and those that are higher
// resolution than required, or lower res than required but not the base layer, so the base layer is always present.
for (Map.Entry<Integer, List<Tile>> tileMapEntry : tileMap.entrySet()) {
for (Tile tile : tileMapEntry.getValue()) {
//不等于当前采样率
if (tile.sampleSize < sampleSize || (tile.sampleSize > sampleSize && tile.sampleSize != fullImageSampleSize)) {
tile.visible = false;
if (tile.bitmap != null) {
tile.bitmap.recycle();
tile.bitmap = null;
}
}
//等于当前采样率
if (tile.sampleSize == sampleSize) {
if (tileVisible(tile)) {
//如果当前tile可见
tile.visible = true;
if (!tile.loading && tile.bitmap == null && load) {
//加载该tile的bitmap
TileLoadTask task = new TileLoadTask(this, decoder, tile);
execute(task);
}
} else if (tile.sampleSize != fullImageSampleSize) {
//并非全图展示的情况下(即缩小到最小的时候),如果当前tile不在可见范围内,回收 //bitmap
tile.visible = false;
if (tile.bitmap != null) {
tile.bitmap.recycle();
tile.bitmap = null;
}
}
} else if (tile.sampleSize == fullImageSampleSize) {
//全图可见时
tile.visible = true;
}
}
}
}
TileLoadTask就是加载tilemap的类,同样,这个类也是继承自AsyncTask
看看TileLoadTask#doInBackground方法
protected Bitmap doInBackground(Void... params) {
try {
SubsamplingScaleImageView view = viewRef.get();
ImageRegionDecoder decoder = decoderRef.get();
Tile tile = tileRef.get();
if (decoder != null && tile != null && view != null && decoder.isReady() && tile.visible) {
view.debug("TileLoadTask.doInBackground, tile.sRect=%s, tile.sampleSize=%d", tile.sRect, tile.sampleSize);
view.decoderLock.readLock().lock();
try {
if (decoder.isReady()) {
// Update tile's file sRect according to rotation
//处理旋转方向
view.fileSRect(tile.sRect, tile.fileSRect);
if (view.sRegion != null) {
//fileSRect即要解码的区域
tile.fileSRect.offset(view.sRegion.left, view.sRegion.top);
}
//解码
return decoder.decodeRegion(tile.fileSRect, tile.sampleSize);
} else {
tile.loading = false;
}
} finally {
view.decoderLock.readLock().unlock();
}
} else if (tile != null) {
tile.loading = false;
}
} catch (Exception e) {
//异常处理,省略
}
return null;
}
接下来就是真正的解码,在SkiaImageRegionDecoder#decodeRegion方法里
public Bitmap decodeRegion(@NonNull Rect sRect, int sampleSize) {
getDecodeLock().lock();
try {
if (decoder != null && !decoder.isRecycled()) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = sampleSize;
options.inPreferredConfig = bitmapConfig;
//decoder即BitmapRegionDecoder,根据传入的区域和采样率进行解码
Bitmap bitmap = decoder.decodeRegion(sRect, options);
if (bitmap == null) {
throw new RuntimeException("Skia image decoder returned null bitmap - image format may not be supported");
}
return bitmap;
} else {
throw new IllegalStateException("Cannot decode region after decoder has been recycled");
}
} finally {
getDecodeLock().unlock();
}
}
继续看TileLoadTask#onPostExecute方法
protected void onPostExecute(Bitmap bitmap) {
final SubsamplingScaleImageView subsamplingScaleImageView = viewRef.get();
final Tile tile = tileRef.get();
if (subsamplingScaleImageView != null && tile != null) {
if (bitmap != null) {
//设置tile的bitmap
tile.bitmap = bitmap;
tile.loading = false;
subsamplingScaleImageView.onTileLoaded();
} else if (exception != null && subsamplingScaleImageView.onImageEventListener != null) {
subsamplingScaleImageView.onImageEventListener.onTileLoadError(exception);
}
}
}
然后是SubsamplingScaleImageView#onTileLoaded方法
private synchronized void onTileLoaded() {
debug("onTileLoaded");
checkReady();
checkImageLoaded();
if (isBaseLayerReady() && bitmap != null) {
if (!bitmapIsCached) {
bitmap.recycle();
}
bitmap = null;
if (onImageEventListener != null && bitmapIsCached) {
onImageEventListener.onPreviewReleased();
}
bitmapIsPreview = false;
bitmapIsCached = false;
}
invalidate();
}
可以看到,这个方法里调用invalidate方法进行了重绘