Android高效加載大圖,長圖和寬圖

對於加載大圖的方法,可以使用如下幾種方式

  1. 採樣(點擊瞭解
  2. 改變編碼來減少內存佔用,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
  1. 使用區域加載的辦法,限制內存使用大小,當手機滑動時,不斷更新加載區域的圖片,相關代碼和註釋如下:
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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章