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 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!
