LargeImageView超大圖的顯示Demo
largeimageview
LargeImageView超大圖的顯示Demo
概述
對于加載圖片,一般為了盡可能避免OOM都會按照如下做法:
- 對于圖片顯示:根據顯示圖片的控件大小對圖片進行壓縮;
- 對于圖片數量非常多:使用LruCache等緩存機制,將一些圖片維持在內存中;
其實對于圖片還有一種加載情況,就是單個圖片非常巨大且不允許壓縮。比如顯示:世界地圖,清明上河圖... 那么對于這種需求該如何實現?首先不壓縮,按照原圖尺寸加載,那么屏幕肯定不夠大,所以肯定是局部加載,那么肯定用到一個類:
BitmapRegionRecoder
其次,既然屏幕顯示不完全,就需要添加Move手勢檢查,讓用戶可以拖動查看。
效果圖
BitmapRegionRecoder簡單使用
BitmapRegionRecoder主要用于顯示圖片的某一塊矩形區域。 BitmapRegionDecoder提供一系列構造方法來初始化該對象,支持傳入文件路徑,文件描述符,文件的inputstream等。例如:
BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
接下來就是顯示指定區域的方法:
bitmapRegionDecoder.decodeRegion(rect, options);
參數一是一個rect,參數二是BitmapFactory.Options,可以控制inSampleSize,inPreferredConfig等。
自定義View顯示大圖
思路:
- 提供一個設置圖片的入口
- 重寫onTouchEvent()方法,根據用戶移動的手勢,去更新顯示區域的Rect
- 每次更新Rect之后,調用invalidate方法,重寫onDraw(),在里面去regionDecoder.decodeRegion(rect, options)實現繪制
上代碼:
public class LargeImageView extends View { /** * BitmapRegionDecoder */ private BitmapRegionDecoder mDecoder; private static final BitmapFactory.Options mDecodeOptions = new BitmapFactory.Options(); static { mDecodeOptions.inPreferredConfig = Bitmap.Config.RGB_565; } private Rect mRect = new Rect(); //圖片的寬高 private int mImageWidth; private int mImageHeight; //檢測Move private MoveGestureDetector mMoveGestureDetector; public LargeImageView(Context context, AttributeSet attrs) { super(context, attrs); init(); } /** * 對外公布的方法,設置圖片 * * @param is */ public void getImageInputStream(InputStream is) { try { //初始化mDecoder mDecoder = BitmapRegionDecoder.newInstance(is, false); //得到圖片的寬高 BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeStream(is, null, options); mImageWidth = options.outWidth; mImageHeight = options.outHeight; requestLayout(); invalidate(); } catch (IOException e) { e.printStackTrace(); }finally { try { if(is != null) { is.close(); } }catch (Exception e) {} } } private void init() { mMoveGestureDetector = new MoveGestureDetector(getContext(), new MoveGestureDetector.SimpleMoveGestureDetector() { @Override public boolean onMove(MoveGestureDetector detector) { //移動rect int movX = (int) detector.getMoveX(); int movY = (int) detector.getMoveY(); if(mImageWidth > getWidth()) { mRect.offset(-movX, 0); checkWidth(); invalidate(); } if(mImageHeight > getHeight()) { mRect.offset(0, -movY); checkHeight(); invalidate(); } return true; } }); } private void checkHeight() { if(mRect.bottom > mImageHeight) { mRect.bottom = mImageHeight; mRect.top = mRect.bottom - getHeight(); } if(mRect.top < 0) { mRect.top = 0; mRect.bottom = mRect.top + getHeight(); } } private void checkWidth() { if(mRect.right > mImageWidth) { mRect.right = mImageWidth; mRect.left = mImageWidth - getWidth(); } if(mRect.left < 0) { mRect.left = 0; mRect.right = mRect.left + getWidth(); } } @Override public boolean onTouchEvent(MotionEvent event) { mMoveGestureDetector.onTouchEvent(event); return true; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = getMeasuredWidth(); int height = getMeasuredHeight(); //初始化mRect,顯示圖片中間區域 mRect.left = mImageWidth/2 - width/2; mRect.top = mImageHeight/2 - height/2; mRect.right = mRect.left + width; mRect.bottom = mRect.top + height; } @Override protected void onDraw(Canvas canvas) { //拿到最新rect對應的bitmap,進行繪制; Bitmap bitmap = mDecoder.decodeRegion(mRect, mDecodeOptions); canvas.drawBitmap(bitmap, 0, 0, null); } }
根據上述代碼
- getImageInputStream里面獲取圖片的真實高度,初始化mDecoder
- onMeasure里面初始化mRect,大小為view的尺寸,并且顯示圖片中間區域
- onTouchEvent里面監聽Move手勢,在監聽的回調里面改變Rect參數,以及邊界檢查,最后invalidate
- onDraw里面拿到最新rect對應的bitmap,進行繪制
OK,上面并不復雜;但是監聽Move的方法有點奇怪:
mMoveGestureDetector.onTouchEvent(event);
嗯,這里模仿了系統的ScaleGestureDetector編寫了MoveGestureDetector
MoveGestureDetector代碼如下:
public class MoveGestureDetector { private Context mContext; private PointF mPrePointer; private PointF mCurPointer; private boolean isGestureMoving; private MotionEvent mPreMotionEvent; private MotionEvent mCurrentMotionEvent; public OnMoveGestureListener mListener; //記錄最終結果返回 private PointF mDeltaPointer = new PointF(); public MoveGestureDetector(Context context, OnMoveGestureListener listener) { this.mContext = context; this.mListener = listener; } public float getMoveX() { return mDeltaPointer.x; } public float getMoveY() { return mDeltaPointer.y; } public boolean onTouchEvent(MotionEvent event) { if(!isGestureMoving) { handleStartEvent(event); }else { handleProgressEvent(event); } return true; } private void handleProgressEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: mListener.onMoveEnd(this); resetState(); break; case MotionEvent.ACTION_MOVE: updateStateByEvent(event); if(mListener.onMove(this)) { mPreMotionEvent.recycle(); mPreMotionEvent = MotionEvent.obtain(event); } break; } } private void handleStartEvent(MotionEvent event) { switch(event.getAction()) { case MotionEvent.ACTION_DOWN: resetState(); mPreMotionEvent = MotionEvent.obtain(event); updateStateByEvent(event); break; case MotionEvent.ACTION_MOVE: isGestureMoving = mListener.onMoveBegin(this); break; } } private void updateStateByEvent(MotionEvent event) { MotionEvent preEvent = mPreMotionEvent; mPrePointer = calculateFocalPointer(preEvent); mCurPointer = calculateFocalPointer(event); boolean skipThisMoveEvent = preEvent.getPointerCount() != event.getPointerCount(); //更新deltaX和deltaY mDeltaPointer.x = skipThisMoveEvent ? 0 : mCurPointer.x - mPrePointer.x; mDeltaPointer.y = skipThisMoveEvent ? 0 : mCurPointer.y - mPrePointer.y; } private PointF calculateFocalPointer(MotionEvent event) { 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); } private void resetState() { if(mPreMotionEvent != null) { mPreMotionEvent.recycle(); mPreMotionEvent = null; } if(mCurrentMotionEvent != null) { mCurrentMotionEvent.recycle(); mCurrentMotionEvent = null; } isGestureMoving = false; } 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) { } } }
簡單分析一下:
- OnMoveGestureListener內部接口以及SimpleMoveGestureDetector內部類都是模仿系統ScaleGestureDetector設計
- 構造方法MoveGestureDetector(Context context, OnMoveGestureListener listener)要求用戶初始化OnMoveGestureListener并傳遞進來
- 對外公布onTouchEvent(),外部必須調用該方法,并把最新的event傳遞進來;
- 對外公布getMoveX(),getMoveY(),外部可以通過這兩個方法,拿到Move時候最新的deltaX和deltaY
- updateStateByEvent(event)根據最新的event,更新mDeltaPointer.x和mDeltaPointer.y
- 剩余的方法:handleStartEvent(MotionEvent);handleProgressEvent(MotionEvent) 主要就是記錄mPreMotionEvent,調用updateStateByEvent(MotionEvent)等來實現邏輯功能
本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!