SubsamplingScaleImageView解析(下)

1,926 阅读15分钟

接上篇 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, 00, 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, 04);
                        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方法进行了重绘