要優雅!Android中這樣加載大圖片和長圖片

咱們在作開發的時候老是會不可避免的遇到加載圖片的狀況,當圖片的尺寸小於ImageView的尺寸的時候,咱們固然能夠很happy的去直接加載展現。c++

可是若是咱們要加載的圖片遠遠大於ImageView的大小,直接用ImageView去展現的話,就會帶來很差的視覺效果,也會佔用太多的內存和性能開銷。程序員

甚至這張圖片足夠大到致使程序oom崩潰。這個時候咱們就須要對圖片進行特殊的處理了:
要優雅!Android中這樣加載大圖片和長圖片面試

#1、圖片壓縮算法

圖片太大,那我就想辦法把它壓縮變小唄。老鐵,這思路徹底沒毛病。canvas

BitmapFactory這個類就提供了多個解析方法(decodeResource、decodeStream、decodeFile等)用於建立Bitmap。網絡

咱們能夠根據圖片的來源來選擇解析方法。架構

  • 好比若是圖片來源於網絡,就可使用decodeStream方法;
  • 若是是sd卡里面的圖片,就能夠選擇decodeFile方法;
  • 若是是資源文件裏面的圖片,就可使用decodeResource方法等。
    這些方法會爲建立的Bitmap分配內存,若是圖片過大的話就會致使 oom。

BitmapFactory爲這些方法都提供了一個可選的參數BitmapFactory.Options,用來輔助咱們解析圖片。這個參數有一個屬性inSampleSize,這個屬性能夠幫助咱們來進行圖片的壓縮。app

爲了解釋inSampleSize的效果,咱們能夠舉個栗子。
好比咱們有一張20481536的圖片,設置inSampleSize的值爲4,就能夠把這張圖片壓縮爲512384,長短各縮小了4倍,所佔內存就縮小了16倍。
這就明瞭了,inSampleSize的做用就是能夠把圖片的長短縮小inSampleSize倍,所佔內存縮小inSampleSize的平方。
官方文檔對於inSampleSize的值也作了一些要求,那就是inSampleSize的值必須大於等於1,若是給定的值小於1,那就默認爲1。
並且inSampleSize的值須要是2的倍數,若是不是的話,就會自動變爲離這個值向下最近的2的倍數的值,好比給定的值是3,那麼最終 inSampleSize的值會是2。ide

固然了,這個inSampleSize的值咱們也不可能隨便就給,最好使咱們能獲取到照片的原始大小,再根據須要進行壓縮。別急,Google 都幫咱們想好了!BitmapFactory.Options有一個屬性inJustDecodeBounds,這個屬性當爲true的時候,代表咱們當前只是爲了獲取當前圖片的邊界的大小,此時BitmapFactory的解析圖片方法的返回值爲 null,該方法是一個十分輕量級的方法。這樣咱們就能夠很愉快的拿到圖片大小了,代碼以下:性能

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; // 當前只爲獲取圖片的邊界大小
BitmapFactory.decodeResource(getResources(), R.drawable.bigpic, options);
int outHeight = options.outHeight;
int outWidth = options.outWidth;
String outMimeType = options.outMimeType;

拿到了圖片的大小,咱們就能夠根據須要計算出所須要壓縮的大小了:

private int caculateSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        int sampleSize = 1;
        int picWidth = options.outWidth;
        int picHeight = options.outHeight;
        if (picWidth > reqWidth || picHeight > reqHeight) {
            int halfPicWidth = picWidth / 2;
            int halfPicHeight = picHeight / 2;
            while (halfPicWidth / sampleSize > reqWidth || halfPicHeight / sampleSize > reqHeight) {
                sampleSize *= 2;
            }
        }
        return sampleSize;
}

下面就是完整的代碼:

mIvBigPic = findViewById(R.id.iv_big_pic);
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true; // 當前只爲獲取圖片的邊界大小
        BitmapFactory.decodeResource(getResources(), R.drawable.bigpic, options);
        int outHeight = options.outHeight;
        int outWidth = options.outWidth;
        String outMimeType = options.outMimeType;
        System.out.println("outHeight = " + outHeight + " outWidth = " + outWidth + " outMimeType = " + outMimeType);
        options.inJustDecodeBounds = false;
        options.inSampleSize = caculateSampleSize(options, getScreenWidth(), getScreenHeight());
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bigpic, options);
        mIvBigPic.setImageBitmap(bitmap);

這樣圖片壓縮到這裏就差很少結束了。

#2、局部展現

有時候咱們經過壓縮能夠取得很好的效果,但有時候效果就不那麼美好了,例如長圖像清明上河圖,像這類的長圖,若是咱們直接壓縮展現的話,這張圖徹底看不清,很影響體驗。這時咱們就能夠採用局部展現,而後滑動查看的方式去展現圖片。

Android裏面是利用BitmapRegionDecoder來局部展現圖片的,展現的是一塊矩形區域。爲了完成這個功能那麼就須要一個方法設置圖片,另外一個方法設置展現的區域。

初始化
BitmapRegionDecoder提供了一系列的newInstance來進行初始化,支持傳入文件路徑,文件描述符和文件流InputStream等

例如:

mRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
上面這個方法解決了傳入圖片,接下來就要去設置展現區域了。

Bitmap bitmap = mRegionDecoder.decodeRegion(mRect, sOptions);
參數一是一個Rect,參數二是BitmapFactory.Options,能夠用來控制inSampleSize,inPreferredConfig等。下面是一個簡單的例子,展現圖片最前面屏幕大的部分:

try {
        BitmapRegionDecoder regionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
        BitmapFactory.Options options1 = new BitmapFactory.Options();
        options1.inPreferredConfig = Bitmap.Config.ARGB_8888;
        Bitmap bitmap = regionDecoder.decodeRegion(new Rect(0, 0, getScreenWidth(), getScreenHeight()), options1);
        mIvBigPic.setImageBitmap(bitmap);
    } catch (IOException e) {
        e.printStackTrace();
    }

固然了,這只是最簡單的用法,對於咱們想要徹底展現圖片並沒什麼用!客官,稍安勿躁,前途已經明瞭!既然咱們能夠實現區域展現,那咱們可不能夠自定義一個View,能夠隨着咱們的手指滑動展現圖片的不一樣區域。yes! of course。那麼咱們就繼續吧!

根據上面的分析,咱們自定義控件的思路就很明白了:

提供一個設置圖片的路口;
重寫onTouchEvent,根據用戶移動的手勢,修改圖片顯示的區域;
每次更新區域參數後,調用invalidate,onDraw裏面去regionDecoder.decodeRegion拿到bitmap,去draw
廢話很少說,直接上代碼:

public class BigImageView extends View {
    private static final String TAG = "BigImageView";

    private BitmapRegionDecoder mRegionDecoder;
    private int mImageWidth, mImageHeight;
    private Rect mRect = new Rect();
    private static BitmapFactory.Options sOptions = new BitmapFactory.Options();
    {
        sOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
    }

    public BigImageView(Context context) {
        this(context, null);
    }

    public BigImageView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BigImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    public void setInputStream(InputStream inputStream) {
        try {
            mRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = false;
            BitmapFactory.decodeStream(inputStream, null, options);
            mImageHeight = options.outHeight;
            mImageWidth = options.outWidth;

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

    int downX = 0;
    int downY = 0;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = (int) event.getX();
                downY = (int) event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                int curX = (int) event.getX();
                int curY = (int) event.getY();

                int moveX = curX - downX;
                int moveY = curY - downY;

                onMove(moveX, moveY);

                System.out.println(TAG + " moveX = " + moveX + " curX = " + curX + " downX = " + downX);

                downX = curX;
                downY = curY;
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return true;
    }

    private void onMove(int moveX, int moveY) {
        if (mImageWidth > getWidth()) {
            mRect.offset(-moveX, 0);
            checkWidth();
            invalidate();
        }

        if (mImageHeight > getHeight()) {
            mRect.offset(0, -moveY);
            checkHeight();
            invalidate();
        }

    }

    private void checkWidth() {
        Rect rect = mRect;
        if (rect.right > mImageWidth) {
            rect.right = mImageWidth;
            rect.left = mImageWidth - getWidth();
        }

        if (rect.left < 0) {
            rect.left = 0;
            rect.right = getWidth();
        }
    }

    private void checkHeight() {
        Rect rect = mRect;
        if (rect.bottom > mImageHeight) {
            rect.bottom = mImageHeight;
            rect.top = mImageHeight - getHeight();
        }

        if (rect.top < 0) {
            rect.top = 0;
            rect.bottom = getWidth();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int width = getMeasuredWidth();
        int height = getMeasuredHeight();

        mRect.left = 0;
        mRect.top = 0;
        mRect.right = width;
        mRect.bottom = height;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Bitmap bitmap = mRegionDecoder.decodeRegion(mRect, sOptions);
        canvas.drawBitmap(bitmap, 0, 0, null);
    }
}

根據上述源碼:

在setInputStream方法裏面初始BitmapRegionDecoder,獲取圖片的實際寬高;

onMeasure方法裏面給Rect賦初始化值,控制開始顯示的圖片區域;

onTouchEvent監聽用戶手勢,修改Rect參數來修改圖片展現區域,而且進行邊界檢測,最後invalidate;
在onDraw裏面根據Rect獲取Bitmap而且繪製。

#最後
學習不是件簡單的事,分享一下咱們阿里p7架構師的學習路線

要優雅!Android中這樣加載大圖片和長圖片

做爲一個Android程序員,要學的東西也不少。

放出來本身整理好的Android學習內容幫助你們學習提高進階

  • 面試題合集
  • 入門級書籍PDF:Java、c、c++
  • Android進階精選書籍PDF
  • 阿里規範文檔
  • Android開發技巧
  • 進階PDF大全
  • 高級進階視頻
  • 源碼
  • 算法學習視頻
  • 未完待續

還有如今的學習趨勢flutter,kotin等的資料,都已經整理好,節省搜索的時間來學習

若是你有須要的話,能夠點贊+評論關注我,而後私信我

要優雅!Android中這樣加載大圖片和長圖片

要優雅!Android中這樣加載大圖片和長圖片

要優雅!Android中這樣加載大圖片和長圖片

相關文章
相關標籤/搜索