要優雅!Android中這樣載入大圖片和長圖片
我們在做開發的時候總是會不可避免的遇到載入圖片的情況,當圖片的尺寸小於ImageView的尺寸的時候,我們當然可以很happy的去直接載入展示。
但是如果我們要載入的圖片遠遠大於ImageView的大小,直接用ImageView去展示的話,就會帶來不好的視覺效果,也會佔用太多的記憶體和效能開銷。
甚至這張圖片足夠大到導致程式oom崩潰。這個時候我們就需要對圖片進行特殊的處理了:
一、圖片壓縮
圖片太大,那我就想辦法把它壓縮變小唄。老鐵,這思路完全沒毛病。
BitmapFactory這個類就提供了多個解析方法(decodeResource、decodeStream、decodeFile等)用於建立Bitmap。
我們可以根據圖片的來源來選擇解析方法。
- 比如如果圖片來源於網路,就可以使用decodeStream方法;
- 如果是sd卡里面的圖片,就可以選擇decodeFile方法;
- 如果是資原始檔裡面的圖片,就可以使用decodeResource方法等。
這些方法會為建立的Bitmap分配記憶體,如果圖片過大的話就會導致 oom。
BitmapFactory為這些方法都提供了一個可選的引數BitmapFactory.Options,用來輔助我們解析圖片。這個引數有一個屬性inSampleSize,這個屬性可以幫助我們來進行圖片的壓縮。
為了解釋inSampleSize的效果,我們可以舉個例子。
比如我們有一張2048
1536的圖片,設定inSampleSize的值為4,就可以把這張圖片壓縮為512384,長短各縮小了4倍,所佔記憶體就縮小了16倍。
這就明瞭了,inSampleSize的作用就是可以把圖片的長短縮小inSampleSize倍,所佔記憶體縮小inSampleSize的平方。
官方文件對於inSampleSize的值也做了一些要求,那就是inSampleSize的值必須大於等於1,如果給定的值小於1,那就預設為1。
而且inSampleSize的值需要是2的倍數,如果不是的話,就會自動變為離這個值向下最近的2的倍數的值,比如給定的值是3,那麼最終 inSampleSize的值會是2。
當然了,這個inSampleSize的值我們也不可能隨便就給,最好使我們能獲取到照片的原始大小,再根據需要進行壓縮。別急,谷歌都幫我們想好了!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);
這樣圖片壓縮到這裡就差不多結束了。
二、區域性展示
有時候我們透過壓縮可以取得很好的效果,但有時候效果就不那麼美好了,例如長影像清明上河圖,像這類的長圖,如果我們直接壓縮展示的話,這張圖完全看不清,很影響體驗。這時我們就可以採用區域性展示,然後滑動檢視的方式去展示圖片。
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學習內容幫助大家學習提升進階
- 面試題合集
- 入門級書籍PDF:Java、c、c++
- Android進階精選書籍PDF
- 阿里規範文件
- Android開發技巧
- 進階PDF大全
- 高階進階影片
- 原始碼
- 演算法學習影片
- 未完待續
還有現在的學習趨勢 flutter,kotin等的資料,都已經整理好,節省搜尋的時間來學習
如果你有需要的話,可以 點贊+評論, 關注我,然後點選 瞭解詳情
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69952849/viewspace-2671019/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Android 圖片載入框架Android框架
- Android 高效安全載入圖片Android
- 圖片預載入,圖片懶載入,和jsonp中的一個疑問JSON
- Android 載入網路圖片 以及實現圓角圖片效果Android
- 圖片預載入和懶載入
- Android偽圖片載入進度效果Android
- Android 基礎之圖片載入(二)Android
- 不一樣的圖片載入方式
- 前端優化之圖片懶載入前端優化
- Flutter圖片載入優化深入探索Flutter優化
- 使用 Alfred 在 markdown 中優雅的使用圖片Alfred
- 圖片懶載入
- 圖片載入事件事件
- 預載入圖片
- Flutter 圖片載入Flutter
- Vue中圖片的載入方式Vue
- Android webview圖片過大AndroidWebView
- Android圖片載入框架Fresco使用詳解Android框架
- 載入本地圖片模糊,Glide載入網路圖片卻很清晰地圖IDE
- 圖片懶載入(IntersectionObserver)Server
- glide圖片載入原理IDE
- 圖片懶載入原理
- 載入遠端圖片
- TestFlight下載App,載入圖片失效。Xcode安裝App,圖片載入正常。APPXCode
- iOS效能優化 - 網路圖片載入優化iOS優化
- 【前端優化】js圖片懶載入及優化前端優化JS
- Android效能優化——圖片優化(二)Android優化
- Android9.0使用Glide載入圖片問題AndroidIDE
- SpringBoot如何優雅的使用@ResponseBody返回圖片Spring Boot
- Android 和 iOS 圖片輪播AndroidiOS
- js:原生多張圖片延遲載入(圖片自己找)JS
- Opencv官方樣例圖片下載OpenCV
- Android圖片突出Android
- 圖片懶載入踩坑
- 解耦圖片載入庫解耦
- 圖片懶載入大白話
- 通用圖片載入元件UniversalImageLoader元件
- Js圖片懶載入(lazyload)JS