自定义View显示超大图片

上一节(自定义初学5——自定义View显示图片已经说了如何自定义View显示图片。做android时,加载图片是避免不了的,加载网络图片还需要异步加载,最烦人的就是经常出现OOM,为了避免这样的问题,我们一般这样解决:
  1. 根据图片控件的大小对图片进行压缩显示。
  2. 如果图片数量非常多,则会使用LruCache等缓存机制,将所有图片占据的内容维持在一个范围内。

有时加载图片还会遇到特殊情况——就是单个图片非常巨大,还不允许压缩。那么对于这种需求,该如何做呢?

首先不压缩,按照原图尺寸加载,那么屏幕肯定是不够大的,并且考虑到内存的情况,不可能一次性整图加载到内存中,所以肯定是局部加载,而有一个类很好的完成了这个功能,这就是BitmapRegionDecoder


先看下官方的API

自定义View显示超大图片


写个简单的例子:
public class MainActivity extends Activity {
    private ImageView mImageView;
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
mImageView = (ImageView) findViewById(R.id.imageView1);
        try {
            InputStream inputStream = getAssets().open("dog.jpg");
            // 获得图片的宽、高
            BitmapFactory.Options tmpOptions = new BitmapFactory.Options();
            // tmpOptions.inJustDecodeBounds = true;// 如果设置为true,解码器将返回null
            // 设置显示图片的中心区域
            BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder
                    .newInstance(inputStream, false);
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
//用给定的Rect和options创建位图
            Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(0, 0,
                    600, 600), options);
            mImageView.setImageBitmap(bitmap);

        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}
效果如下:
自定义View显示超大图片

很明显,BitmapRegionDecoder的功能就是加载图片的部分区域,现在大概原理就清楚了,我们需要定义一个View,然后利用去BitmapRegionDecoder显示一个图片的局部,然后利用手势检测,能够移动图片,好了,开始实现吧。梳理一下我们的View:
  • 提供一个设置图片的入口
  • 重写onTouchEvent,在里面根据用户移动的手势,去更新显示区域的参数
  • 每次更新区域参数后,调用invalidate,onDraw里面去regionDecoder.decodeRegion拿到bitmap,去draw

自定义LargeImageView
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.ARGB_8888;
    }

    public void setInputStream(InputStream is) {
        try {
            mDecoder = BitmapRegionDecoder.newInstance(is, false);
            BitmapFactory.Options tmpOptions = new BitmapFactory.Options();
            // 获取图片的宽高
            tmpOptions.inJustDecodeBounds = true;
            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;

        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 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) {
        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;

    }
}

手势检测抽象类BaseGestureDetector
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 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);

        boolean mSkipThisMoveEvent = prev.getPointerCount() != event
                .getPointerCount();

        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) {
        }
    }
}
用法不说了,看效果吧:
自定义View显示超大图片
这只是简单的实现,我们当然还可以实现更多的功能了,我就抛砖引玉吧

源代码
参考:

上一篇:固定高度div,随内容自动变高css定义方法


下一篇:3.6 自定义View (3.6.1)