Android加载大图

Q:给定一个1000*20000的图片(宽1000dp,高20000dp),如何正常加载显示,并且不发生OOM异常.
 
解决这个问题有两种思路,第一种是使用BitmapFactory.Options的采样率inSampleSize,二是使用BitmapRegionDecoder每次加载一个矩形区域.

采样率加载

BitmapFactory.Options的几个字段
1.inJustDecodeBounds:为true的时候不会加载图片(返回一个null),但是会填充outXXX信息,常用来在不分配内存的情况下获取图片的宽高.
2.inSampleSize:采样率是用来对图片进行缩放的.如果采样率=4,加载到内存的图片宽高都会变成原来的1/4,内存则变成了原来图片的1/16.采样率只有在>1的时候才会对图片进行压缩.   <=1的时候会把他看做是1.它使用的是2的幂次数,如果不是会向下取到2的幂次数.
 
这种方法是通过对图片按照一定的比例缩放,从而达到减少内存的效果.
 private void setLargeViewWithInSampleSize() {
        BitmapFactory.Options options = new BitmapFactory.Options();
        //不获取图像,仅仅获取图片的一些信息,比如宽高
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(getInputStream("world.jpg"), null, options);
        int imageWidth = options.outWidth;
        int imageHeight = options.outHeight;
​
        //获取屏幕宽高,然后根据屏幕大小设置采样率
        DisplayMetrics displayMetrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
        int screenWidth = displayMetrics.widthPixels;
        int screenHeight = displayMetrics.heightPixels;
​
        //计算采样率
        int scaleX = imageWidth / screenWidth;
        int scaleY = imageHeight / screenHeight;
        int scale = 1;
​
        if (scaleX > scaleY && scaleY >= 1) {
            scale = scaleX;
        }
        if (scaleX < scaleY && scaleX >= 1) {
            scale = scaleY;
        }
​
        options.inJustDecodeBounds = false;
        options.inSampleSize = scale;
        Bitmap bitmap = BitmapFactory.decodeStream(getInputStream("world.jpg"), null, options);
        largeView.setImageBitmap(bitmap);
    }
​
    private InputStream getInputStream(String name) {
        InputStream inputStream = null;
        try {
            inputStream = getAssets().open(name);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return inputStream;
    }

图片按区域加载

如果需求是完全展示,不对图片进行压缩,那么上面的方案就无法满足我们了,Android提供给了我们一个类BitmapRegionDecoder,它可以让我们每次加载一个矩形区域.
思路:主要思路就是在onTouchEvent中根据手势去用BitmapRegionDecoder加载imageView大小的图片区域.
具体的几个类如下,下面的做法是提升了手势事件的重用性,也可以将手势处理的过程直接写在onTouchEvent中.

public abstract class BaseGestureDetector {
​
    protected boolean mGestureInProgress;
​
    protected MotionEvent mPreMotionEvent;
    protected MotionEvent mCurrentMotionEvent;
​
    protected Context mContext;
​
    public BaseGestureDetector(Context context) {
        mContext = context;
    }
​
​
    public boolean onToucEvent(MotionEvent event) {
​
        if (!mGestureInProgress) {
            handleStartProgressEvent(event);
        } else {
            handleInProgressEvent(event);
        }
​
        return true;
​
    }
​
    protected abstract void handleInProgressEvent(MotionEvent event);
​
    protected abstract void handleStartProgressEvent(MotionEvent event);
​
    protected abstract void updateStateByEvent(MotionEvent event);
​
    protected void resetState() {
        if (mPreMotionEvent != null) {
            mPreMotionEvent.recycle();
            mPreMotionEvent = null;
        }
        if (mCurrentMotionEvent != null) {
            mCurrentMotionEvent.recycle();
            mCurrentMotionEvent = null;
        }
        mGestureInProgress = false;
    }
}
public class MoveGestureDetector extends BaseGestureDetector {
​
    private PointF mCurrentPointer;
    private PointF mPrePointer;
    //仅仅为了减少创建内存
    private PointF mDeltaPointer = new PointF();
​
    //用于记录最终结果,并返回
    private PointF mExtenalPointer = new PointF();
​
    private OnMoveGestureListener mListenter;
​
​
    public MoveGestureDetector(Context context, OnMoveGestureListener listener) {
        super(context);
        mListenter = listener;
    }
​
    @Override
    protected void handleInProgressEvent(MotionEvent event) {
        int actionCode = event.getAction() & MotionEvent.ACTION_MASK;
        switch (actionCode) {
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                mListenter.onMoveEnd(this);
                resetState();
                break;
            case MotionEvent.ACTION_MOVE:
                updateStateByEvent(event);
                boolean update = mListenter.onMove(this);
                if (update) {
                    mPreMotionEvent.recycle();
                    mPreMotionEvent = MotionEvent.obtain(event);
                }
                break;
        }
    }
​
    @Override
    protected void handleStartProgressEvent(MotionEvent event) {
        int actionCode = event.getAction() & MotionEvent.ACTION_MASK;
        switch (actionCode) {
            case MotionEvent.ACTION_DOWN:
                resetState();//防止没有接收到CANCEL or UP ,保险起见
                mPreMotionEvent = MotionEvent.obtain(event);
                updateStateByEvent(event);
                break;
            case MotionEvent.ACTION_MOVE:
                mGestureInProgress = mListenter.onMoveBegin(this);
                break;
        }
​
    }
​
    protected void updateStateByEvent(MotionEvent event) {
        final MotionEvent prev = mPreMotionEvent;
​
        mPrePointer = caculateFocalPointer(prev);
        mCurrentPointer = caculateFocalPointer(event);
​
        //Log.e("TAG", mPrePointer.toString() + " ,  " + mCurrentPointer);
​
        boolean mSkipThisMoveEvent = prev.getPointerCount() != event.getPointerCount();
​
        //Log.e("TAG", "mSkipThisMoveEvent = " + mSkipThisMoveEvent);
        mExtenalPointer.x = mSkipThisMoveEvent ? 0 : mCurrentPointer.x - mPrePointer.x;
        mExtenalPointer.y = mSkipThisMoveEvent ? 0 : mCurrentPointer.y - mPrePointer.y;
​
    }
​
    /**
     * 根据event计算多指中心点
     *
     * @param event
     * @return
     */
    private PointF caculateFocalPointer(MotionEvent event) {
        final int count = event.getPointerCount();
        float x = 0, y = 0;
        for (int i = 0; i < count; i++) {
            x += event.getX(i);
            y += event.getY(i);
        }
​
        x /= count;
        y /= count;
​
        return new PointF(x, y);
    }
​
​
    public float getMoveX() {
        return mExtenalPointer.x;
​
    }
​
    public float getMoveY() {
        return mExtenalPointer.y;
    }
​
​
    public interface OnMoveGestureListener {
        public boolean onMoveBegin(MoveGestureDetector detector);
​
        public boolean onMove(MoveGestureDetector detector);
​
        public void onMoveEnd(MoveGestureDetector detector);
    }
​
    public static class SimpleMoveGestureDetector implements OnMoveGestureListener {
​
        @Override
        public boolean onMoveBegin(MoveGestureDetector detector) {
            return true;
        }
​
        @Override
        public boolean onMove(MoveGestureDetector detector) {
            return false;
        }
​
        @Override
        public void onMoveEnd(MoveGestureDetector detector) {
        }
    }
​
}
public class LargeImageView extends View {
    private BitmapRegionDecoder mDecoder;
    /**
     * 图片的宽度和高度
     */
    private int mImageWidth, mImageHeight;
    /**
     * 绘制的区域
     */
    private volatile Rect mRect = new Rect();
​
    private MoveGestureDetector mDetector;
​
​
    private static final BitmapFactory.Options options = new BitmapFactory.Options();
​
    static {
        options.inPreferredConfig = Bitmap.Config.RGB_565;
    }
​
    public void setInputStream(InputStream is) {
        try {
            mDecoder = BitmapRegionDecoder.newInstance(is, false);
            BitmapFactory.Options tmpOptions = new BitmapFactory.Options();
            // Grab the bounds for the scene dimensions
            tmpOptions.inJustDecodeBounds = true;
            //解决获取图片宽高=-1.
            is.reset();
​
            BitmapFactory.decodeStream(is, null, tmpOptions);
            mImageWidth = tmpOptions.outWidth;
            mImageHeight = tmpOptions.outHeight;
​​
            requestLayout();
            invalidate();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
​
            try {
                if (is != null) is.close();
            } catch (Exception e) {
            }
        }
    }
​
​
    public void init() {
        mDetector = new MoveGestureDetector(getContext(), new MoveGestureDetector.SimpleMoveGestureDetector() {
            @Override
            public boolean onMove(MoveGestureDetector detector) {
                int moveX = (int) detector.getMoveX();
                int moveY = (int) detector.getMoveY();
​
                if (mImageWidth > getWidth()) {
                    mRect.offset(-moveX, 0);
                    checkWidth();
                    invalidate();
                }
                if (mImageHeight > getHeight()) {
                    mRect.offset(0, -moveY);
                    checkHeight();
                    invalidate();
                }
​
                return true;
            }
        });
    }
​
​
    private void checkWidth() {
​
​
        Rect rect = mRect;
        int imageWidth = mImageWidth;
        int imageHeight = mImageHeight;
​
        if (rect.right > imageWidth) {
            rect.right = imageWidth;
            rect.left = imageWidth - getWidth();
        }
​
        if (rect.left < 0) {
            rect.left = 0;
            rect.right = getWidth();
        }
    }
​
​
    private void checkHeight() {
​
        Rect rect = mRect;
        int imageWidth = mImageWidth;
        int imageHeight = mImageHeight;
​
        if (rect.bottom > imageHeight) {
            rect.bottom = imageHeight;
            rect.top = imageHeight - getHeight();
        }
​
        if (rect.top < 0) {
            rect.top = 0;
            rect.bottom = getHeight();
        }
    }
​
​
    public LargeImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
​
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mDetector.onToucEvent(event);
        return true;
    }
​
    @Override
    protected void onDraw(Canvas canvas) {
        if (mDecoder != null) {
            Bitmap bm = mDecoder.decodeRegion(mRect, options);
            canvas.drawBitmap(bm, 0, 0, null);
        }
    }
​
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
​
        int width = getMeasuredWidth();
        int height = getMeasuredHeight();
​
        int imageWidth = mImageWidth;
        int imageHeight = mImageHeight;
​
        //默认直接显示图片的中心区域,可以自己去调节
        mRect.left = imageWidth / 2 - width / 2;
        mRect.top = imageHeight / 2 - height / 2;
        mRect.right = mRect.left + width;
        mRect.bottom = mRect.top + height;
​
    }
​
}

猜你喜欢

转载自www.cnblogs.com/coconna/p/9258798.html