溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

要優(yōu)雅!Android中這樣加載大圖片和長(zhǎng)圖片

發(fā)布時(shí)間:2020-08-07 11:07:16 來(lái)源:ITPUB博客 閱讀:207 作者:yilian 欄目:移動(dòng)開發(fā)

我們?cè)谧鲩_發(fā)的時(shí)候總是會(huì)不可避免的遇到加載圖片的情況,當(dāng)圖片的尺寸小于ImageView的尺寸的時(shí)候,我們當(dāng)然可以很happy的去直接加載展示。

但是如果我們要加載的圖片遠(yuǎn)遠(yuǎn)大于ImageView的大小,直接用ImageView去展示的話,就會(huì)帶來(lái)不好的視覺(jué)效果,也會(huì)占用太多的內(nèi)存和性能開銷。

甚至這張圖片足夠大到導(dǎo)致程序oom崩潰。這個(gè)時(shí)候我們就需要對(duì)圖片進(jìn)行特殊的處理了:

要優(yōu)雅!Android中這樣加載大圖片和長(zhǎng)圖片

一、圖片壓縮

圖片太大,那我就想辦法把它壓縮變小唄。老鐵,這思路完全沒(méi)毛病。

BitmapFactory這個(gè)類就提供了多個(gè)解析方法(decodeResource、decodeStream、decodeFile等)用于創(chuàng)建Bitmap。

我們可以根據(jù)圖片的來(lái)源來(lái)選擇解析方法。

  • 比如如果圖片來(lái)源于網(wǎng)絡(luò),就可以使用decodeStream方法;
  • 如果是sd卡里面的圖片,就可以選擇decodeFile方法;
  • 如果是資源文件里面的圖片,就可以使用decodeResource方法等。
    這些方法會(huì)為創(chuàng)建的Bitmap分配內(nèi)存,如果圖片過(guò)大的話就會(huì)導(dǎo)致 oom。

BitmapFactory為這些方法都提供了一個(gè)可選的參數(shù)BitmapFactory.Options,用來(lái)輔助我們解析圖片。這個(gè)參數(shù)有一個(gè)屬性inSampleSize,這個(gè)屬性可以幫助我們來(lái)進(jìn)行圖片的壓縮。

為了解釋inSampleSize的效果,我們可以舉個(gè)栗子。
比如我們有一張2048 1536的圖片,設(shè)置inSampleSize的值為4,就可以把這張圖片壓縮為512384,長(zhǎng)短各縮小了4倍,所占內(nèi)存就縮小了16倍。
這就明了了,inSampleSize的作用就是可以把圖片的長(zhǎng)短縮小inSampleSize倍,所占內(nèi)存縮小inSampleSize的平方。
官方文檔對(duì)于inSampleSize的值也做了一些要求,那就是inSampleSize的值必須大于等于1,如果給定的值小于1,那就默認(rèn)為1。
而且inSampleSize的值需要是2的倍數(shù),如果不是的話,就會(huì)自動(dòng)變?yōu)殡x這個(gè)值向下最近的2的倍數(shù)的值,比如給定的值是3,那么最終 inSampleSize的值會(huì)是2。

當(dāng)然了,這個(gè)inSampleSize的值我們也不可能隨便就給,最好使我們能獲取到照片的原始大小,再根據(jù)需要進(jìn)行壓縮。別急,谷歌都幫我們想好了!BitmapFactory.Options有一個(gè)屬性inJustDecodeBounds,這個(gè)屬性當(dāng)為true的時(shí)候,表明我們當(dāng)前只是為了獲取當(dāng)前圖片的邊界的大小,此時(shí)BitmapFactory的解析圖片方法的返回值為 null,該方法是一個(gè)十分輕量級(jí)的方法。這樣我們就可以很愉快的拿到圖片大小了,代碼如下:

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

拿到了圖片的大小,我們就可以根據(jù)需要計(jì)算出所需要壓縮的大小了:

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; // 當(dāng)前只為獲取圖片的邊界大小
        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);

這樣圖片壓縮到這里就差不多結(jié)束了。

二、局部展示

有時(shí)候我們通過(guò)壓縮可以取得很好的效果,但有時(shí)候效果就不那么美好了,例如長(zhǎng)圖像清明上河圖,像這類的長(zhǎng)圖,如果我們直接壓縮展示的話,這張圖完全看不清,很影響體驗(yàn)。這時(shí)我們就可以采用局部展示,然后滑動(dòng)查看的方式去展示圖片。

Android里面是利用BitmapRegionDecoder來(lái)局部展示圖片的,展示的是一塊矩形區(qū)域。為了完成這個(gè)功能那么就需要一個(gè)方法設(shè)置圖片,另一個(gè)方法設(shè)置展示的區(qū)域。

初始化
BitmapRegionDecoder提供了一系列的newInstance來(lái)進(jìn)行初始化,支持傳入文件路徑,文件描述符和文件流InputStream等

例如:

mRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
上面這個(gè)方法解決了傳入圖片,接下來(lái)就要去設(shè)置展示區(qū)域了。

Bitmap bitmap = mRegionDecoder.decodeRegion(mRect, sOptions);
參數(shù)一是一個(gè)Rect,參數(shù)二是BitmapFactory.Options,可以用來(lái)控制inSampleSize,inPreferredConfig等。下面是一個(gè)簡(jiǎn)單的例子,展示圖片最前面屏幕大的部分:

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();
    }

當(dāng)然了,這只是最簡(jiǎn)單的用法,對(duì)于我們想要完全展示圖片并沒(méi)什么用!客官,稍安勿躁,前途已經(jīng)明了!既然我們可以實(shí)現(xiàn)區(qū)域展示,那我們可不可以自定義一個(gè)View,可以隨著我們的手指滑動(dòng)展示圖片的不同區(qū)域。yes! of course。那么我們就繼續(xù)吧!

根據(jù)上面的分析,我們自定義控件的思路就很明白了:

提供一個(gè)設(shè)置圖片的路口;
重寫onTouchEvent,根據(jù)用戶移動(dòng)的手勢(shì),修改圖片顯示的區(qū)域;
每次更新區(qū)域參數(shù)后,調(diào)用invalidate,onDraw里面去regionDecoder.decodeRegion拿到bitmap,去draw
廢話不多說(shuō),直接上代碼:

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);
    }
}

根據(jù)上述源碼:

在setInputStream方法里面初始BitmapRegionDecoder,獲取圖片的實(shí)際寬高;

onMeasure方法里面給Rect賦初始化值,控制開始顯示的圖片區(qū)域;

onTouchEvent監(jiān)聽用戶手勢(shì),修改Rect參數(shù)來(lái)修改圖片展示區(qū)域,并且進(jìn)行邊界檢測(cè),最后invalidate;
在onDraw里面根據(jù)Rect獲取Bitmap并且繪制。

最后

學(xué)習(xí)不是件簡(jiǎn)單的事,分享一下我們阿里p7架構(gòu)師的學(xué)習(xí)路線

要優(yōu)雅!Android中這樣加載大圖片和長(zhǎng)圖片

作為一個(gè)Android程序員,要學(xué)的東西也很多。

放出來(lái)自己整理好的Android學(xué)習(xí)內(nèi)容幫助大家學(xué)習(xí)提升進(jìn)階

  • 面試題合集
  • 入門級(jí)書籍PDF:Java、c、c++
  • Android進(jìn)階精選書籍PDF
  • 阿里規(guī)范文檔
  • Android開發(fā)技巧
  • 進(jìn)階PDF大全
  • 高級(jí)進(jìn)階視頻
  • 源碼
  • 算法學(xué)習(xí)視頻
  • 未完待續(xù)

還有現(xiàn)在的學(xué)習(xí)趨勢(shì) flutter,kotin等的資料,都已經(jīng)整理好,節(jié)省搜索的時(shí)間來(lái)學(xué)習(xí)

如果你有需要的話,可以 點(diǎn)贊+評(píng)論關(guān)注我,然后點(diǎn)擊 了解詳情

要優(yōu)雅!Android中這樣加載大圖片和長(zhǎng)圖片
要優(yōu)雅!Android中這樣加載大圖片和長(zhǎng)圖片
要優(yōu)雅!Android中這樣加載大圖片和長(zhǎng)圖片
向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI