Android加載超大圖實戰

如果是高清大圖,那就說明不允許進行圖片壓縮,比如微博長圖,清明上河圖。

所以我們就要對圖片進行局部顯示,這就用到BitmapRegionDecoder屬性,主要用於顯示圖片的某一塊矩形區域。實際項目使用中,我們可以根據手勢滑動,然後不斷更新我們的Rect參數來實現具體的功能即可。

效果如下:
BitmapRegionDecoder類:
public final class BitmapRegionDecoder {
}
BitmapRegionDecoder構造方法:
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    private BitmapRegionDecoder(long decoder) {
        mNativeBitmapRegionDecoder = decoder;
        mRecycled = false;
    }

BitmapRegionDecoder 類被final進行修飾,因此無法被繼承。構造方法被私有化,無法直接new對象。

BitmapRegionDecoder 提供了一系列newInstance,最終都是通過數據流來得到對象。很好理解,加載圖片肯定要有圖片文件纔行。

// 創建 BitmapRegionDecoder 對象方法
static BitmapRegionDecoder newInstance(InputStream is, boolean isShareable)
static BitmapRegionDecoder newInstance(FileDescriptor fd, boolean isShareable)
static BitmapRegionDecoder newInstance(String pathName, boolean isShareable)
static BitmapRegionDecoder newInstance(byte[] data, int offset, int length, boolean isShareable)

使用decodeRegion方法解碼一個矩形區域來返回bitmap,矩形區域可以根據手勢拖動或者縮放來獲取。

    public Bitmap decodeRegion(Rect rect, BitmapFactory.Options options) {
        BitmapFactory.Options.validate(options);
        synchronized (mNativeLock) {
            checkRecycled("decodeRegion called on recycled region decoder");
            if (rect.right <= 0 || rect.bottom <= 0 || rect.left >= getWidth()
                    || rect.top >= getHeight())
                throw new IllegalArgumentException("rectangle is outside the image");
            return nativeDecodeRegion(mNativeBitmapRegionDecoder, rect.left, rect.top,
                    rect.right - rect.left, rect.bottom - rect.top, options,
                    BitmapFactory.Options.nativeInBitmap(options),
                    BitmapFactory.Options.nativeColorSpace(options));
        }
    }

通過onTouchEvent對滑動手勢修改decodeRegion的區域,進行區域加載改變:

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = event.getX();
                downY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                dx = downX - event.getRawX();
                dy = downY - event.getRawY();
                updateRegion((int)dx, (int)dy);
                break;
        }
        return true;
    }

然後在onDraw方法中進行繪製bitmap:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        mOptions.inBitmap = mBitmap;
        Rect rect = new Rect(rectLeft, rectTop, rectRight, rectBottom);
        mMatrix.setScale(1, 1);
        mBitmap = mRegionDecoder.decodeRegion(rect, mOptions);
        canvas.drawBitmap(mBitmap, mMatrix, null);
    }

支持的圖像格式 : 目前圖像區域解碼對象只支持 JPEG 和 PNG 兩種圖像格式。

這裏寫了一個自定義ImageView來加載區域圖片,完整代碼:

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.widget.AppCompatImageView;
import java.io.IOException;
import java.io.InputStream;

public class RegionLoadImageView extends AppCompatImageView {
    private BitmapRegionDecoder mRegionDecoder;
    private BitmapFactory.Options mOptions = new BitmapFactory.Options();
    private Bitmap mBitmap;
    private Matrix mMatrix = new Matrix();
    private InputStream inputStream;
    private int rectLeft, rectTop, rectRight, rectBottom;
    private int measuredWidht, measuredHeight;
    private float dx, dy, downX, downY;

    public RegionLoadImageView(Context context) {
        super(context);
    }

    public RegionLoadImageView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public RegionLoadImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @RequiresApi(api = Build.VERSION_CODES.S)
    public void setImageRes(int imageRes) {
        inputStream = getResources().openRawResource(imageRes);
        setRegionDecoder(inputStream);
    }

    /**
     * 設置區域解碼器
     */
    @RequiresApi(api = Build.VERSION_CODES.S)
    public void setRegionDecoder(InputStream inputStream) {
        try {
            mRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measuredWidht = getMeasuredWidth();
        measuredHeight = getMeasuredHeight();
        updateRegion(0, 0);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = event.getX();
                downY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                dx = downX - event.getRawX();
                dy = downY - event.getRawY();
                updateRegion((int)dx, (int)dy);
                break;
        }
        return true;
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        try {
            if (inputStream != null) {
                inputStream.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        mOptions.inBitmap = mBitmap;
        Rect rect = new Rect(rectLeft, rectTop, rectRight, rectBottom);
        mMatrix.setScale(1, 1);
        mBitmap = mRegionDecoder.decodeRegion(rect, mOptions);
        canvas.drawBitmap(mBitmap, mMatrix, null);
    }

    public void updateRegion(int x, int y) {
        rectLeft = x;
        rectTop = y;
        rectRight = x + measuredWidht;
        rectBottom = y + measuredHeight;
        invalidate();
    }
}

最後,給該自定義view設置加載大圖資源,資源放在res/raw文件夾下:

regionLoadImageView?.setImageRes(R.raw.super_long)

使用的大圖資源:


參考:

https://www.jianshu.com/p/613fac562145

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章