對於加載大圖的方法,可以使用如下幾種方式
- 採樣(點擊瞭解)
- 改變編碼來減少內存佔用,RGB565比ARGB_8888需要內存少
ARGB_8888代表 4個8位,需要32位,也就是4個字節,需要內存爲:長x寬x4
ARGB_4444代表 4個4位,需要16位,也就是2個字節,需要內存爲:長x寬x2
RGB565 一共5+6+5 = 16位,也就是2個字節,沒有ALPHA通道,需要內存爲:長x寬x2
- 使用區域加載的辦法,限制內存使用大小,當手機滑動時,不斷更新加載區域的圖片,相關代碼和註釋如下:
public class PandoraView extends View implements GestureDetector.OnGestureListener, View.OnTouchListener {
private GestureDetector mGestureDetector;
private BitmapFactory.Options mOptions;
private Rect mRect;
private Scroller mScroller;
private int mImageWidth;
private int mImageHeight;
private int mViewWidth;
private int mViewHeight;
private float mScale;
private BitmapRegionDecoder mDecoder;
private Bitmap mBitmap;
private boolean isLongImage; //true: 長圖 false: 寬圖
private Matrix matrix = new Matrix();
public PandoraView(Context context) {
this(context, null, 0);
}
public PandoraView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public PandoraView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//第一步:設置PandoraView的成員變量
mRect = new Rect();
//內存複用
mOptions = new BitmapFactory.Options();
//手勢識別
mGestureDetector = new GestureDetector(context, this);
//滾動類
mScroller = new Scroller(context);
setOnTouchListener(this);
}
// 2.設置圖片,得到圖片的信息
public void setImage(InputStream is) {
// 獲取圖片的寬和高,注意:不能將圖片整個加載進內存
mOptions.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, mOptions);
mImageWidth = mOptions.outWidth;
mImageHeight = mOptions.outHeight;
// 開啓複用
mOptions.inMutable = true;
// 設置格式爲RGB565 所佔內存比ARGB_8888小
mOptions.inPreferredConfig = Bitmap.Config.RGB_565;
mOptions.inJustDecodeBounds = false;
//判斷是長圖還是寬圖
isLongImage = mImageHeight > mImageWidth;
// 區域解碼器
try {
mDecoder = BitmapRegionDecoder.newInstance(is, false);
} catch (IOException e) {
e.printStackTrace();
}
requestLayout();
}
// 3.開始測量,得到View的寬和高,測量加載的圖片到底要縮放成什麼樣子
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 得到View的寬和高
mViewWidth = getMeasuredWidth();
mViewHeight = getMeasuredHeight();
// 確定要加載圖片的區域
mRect.left = 0;
mRect.top = 0;
if (isLongImage) {
mRect.right = mImageWidth;
// 計算縮放因子
mScale = mViewWidth / (float) mImageWidth;
mRect.bottom = (int) (mViewHeight / mScale);
} else {
mRect.bottom = mImageHeight;
// 計算縮放因子
mScale = mViewHeight / (float) mImageHeight;
mRect.right = (int) (mViewWidth / mScale);
}
// 得到一個矩陣進行縮放,相當於得到View的大小
matrix.setScale(mScale, mScale);
}
// 4.畫出具體的內容
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 判斷解碼器是不是爲null,如果解碼器沒有拿到,表示沒有設置過圖片
if (mDecoder == null) {
return;
}
// 真正的內存複用 注意:複用的bitmap必須和即將解碼的bitmap尺寸一樣
mOptions.inBitmap = mBitmap;
// 指定解碼區域
mBitmap = mDecoder.decodeRegion(mRect, mOptions);
canvas.drawBitmap(mBitmap, matrix, null);
}
// 5.處理點擊事件
@Override
public boolean onTouch(View v, MotionEvent event) {
// 直接將事件交給手勢事件處理
return mGestureDetector.onTouchEvent(event);
}
// 6.手按下去
@Override
public boolean onDown(MotionEvent e) {
// 如果移動沒有停止,強制停止
if (!mScroller.isFinished()) {
mScroller.forceFinished(true);
}
// 繼續接受後續事件
return true;
}
// 7.處理滑動事件
/**
* @param e1 開始事件,手指按下去,開始獲取座標
* @param e2 獲取當前事件座標
* @param distanceX x軸移動的距離
* @param distanceY y軸移動的距離
*/
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (isLongImage){
// 上下滑動時,mRect需要改變顯示的區域
mRect.offset(0, Math.round(distanceY));
// 移動時,處理到達底部和頂部的情況
if (mRect.bottom > mImageHeight) {
mRect.bottom = mImageHeight;
mRect.top = mImageHeight - (int) (mViewHeight / mScale);
}
if (mRect.top < 0) {
mRect.top = 0;
mRect.bottom = (int) (mViewHeight / mScale);
}
} else {
// 左右滑動時,mRect需要改變顯示的區域
mRect.offset(Math.round(distanceX), 0);
// 移動時,處理到達左邊和右邊·的情況
if (mRect.right > mImageWidth) {
mRect.right = mImageWidth;
mRect.left = mImageWidth - (int) (mViewWidth / mScale);
}
if (mRect.left < 0) {
mRect.left = 0;
mRect.right = (int) (mViewWidth / mScale);
}
}
invalidate();
return false;
}
// 8.處理慣性問題
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (isLongImage) {
mScroller.fling(0, mRect.top, 0, (int) (-velocityY), 0, 0, 0,
mImageHeight - (int) (mViewHeight / mScale));
} else {
mScroller.fling(mRect.left, 0, (int) (-velocityX), 0, 0, mImageWidth
- (int) (mViewWidth / mScale), 0, 0);
}
return false;
}
// 9.處理計算結果
@Override
public void computeScroll() {
if (mScroller.isFinished()) {
return;
}
if (mScroller.computeScrollOffset()) {
if (isLongImage) {
mRect.top = mScroller.getCurrY();
mRect.bottom = mRect.top + (int) (mViewHeight / mScale);
} else {
mRect.left = mScroller.getCurrX();
mRect.right = mRect.left + (int) (mViewWidth / mScale);
}
invalidate();
}
}
}
Demo共享於GitHub