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

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