編輯:關於Android編程
筆者近來在學習Android自定義View,收集了一些不算復雜但又“長得”還可以的自定義View效果實現,之前分享過兩個效果:一個水平的進度條,一個圓形溫度顯示器
今天我要來分享的是這樣的效果——圖片合並
當你使用微信進入某一個人的朋友圈列表,如果他們某條信息所攜帶的圖片超過一張,就會做成如圖所示這種顯示效果,是將多張圖片合並在一起形成的預覽效果。當然對於朋友圈的實現方式我不清楚,有可能是服務器端已經合並好圖片了,返回給客戶端的就是一張合並之後的圖片,只要一個ImageView就可以了。不過對於自定義View的學習,你是否想過,如果服務器給你的是若干張圖片,你要自己拼接成一張,該怎麼搞,今天我們來講這個。
其實在繪圖方面本文所牽扯到的知識點還沒有多少,反而是關於bitmap的內存占用,控制顯得更加重要。總的來說大概如下:
1. canvas.drawBitmap()方法裡面各個參數含義。
2. 如果要顯示的圖片區域(或者像素點)明顯的比原圖片小(因為我們只是做縮略圖),怎麼節約內存使用。這將涉及到加載”bitmap”時利用”BitmapFactory.Options”對象計算壓縮的比例等等知識。
3. 一些很簡單的數學計算,如前面的圖片所示,是需要在一、二、三、四張圖片的情況下,計算裁剪圖片大小以及擺放在什麼樣的位置上。
想象一下,如果我現在給你一張白紙(canvas,其實如果我說一個相框,可能更方便你想象),再給你一到四張照片,你要怎麼“擺”出我的截圖裡的效果。其實很簡單,筆者提出思路如下:
1. 首先你需要判斷下圖片張數,然後分別進行處理。
2. 如果是一張圖片,我們就將整張原圖“繪制”到我們的canvas上面。
3. 如果是兩張圖片,我們就將它們分別橫向壓縮一半,然後分別繪制到canvas裡面,每張圖片占一半位置。
4. 如果是三張圖片,將第一張壓縮一半,繪制到canvas的左半邊,另外兩張圖片壓縮成原來的四分之一,繪制到canvas右上角,右下角。
5. 如果是四張圖片,將四張圖片全部都壓縮成原來的四分之一,繪制到canvas的四個角上。
6. 為了美觀,圖片之間畫條白線分隔一下。
關於圖片源,在你的項目當中,圖片來源大多應該來自網絡,不過作為一個樣例,貪圖方便(方便偷懶),圖片來源直接來自本地,在drawable文件夾下面,所以我在生成bitmap時,調用的是”BitmapFactory.decodeResource()”方法。如果你的圖片來自網絡,可能需要別的方法,還有,如果你是用glide之類網絡框架下載圖片,請搞清楚這些框架下載圖片之後對圖片所做的事情,筆者曾經在實際項目裡,使用一些框架導致數據錯亂,需要另外進行其他調試。
一張圖片時的代碼片段if(length == 1){ //如果只有一張圖片,則將該圖片裁剪成合適大小,直接繪制就可以了 bitmap = decodeSampledBitmapFromResource(drawableIds[0], measuredWidth, measuredHeight); //要截取的原圖片的范圍 srcRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight()); //圖片繪制在canvas上的范圍 dstRect.set(0, 0, measuredWidth, measuredHeight); canvas.drawBitmap(bitmap, srcRect, dstRect, bitmapPaint); }兩張圖片時的代碼片段
if(length == 2){ //如果有兩張圖片,則兩張圖片各占左右一半位置,中間畫一條分隔線 //兩張圖片中間分隔線的寬度 int lineWidth = 4; //圖片的目標寬度 int dstWidth = (measuredWidth-lineWidth)/2; //繪制第一張圖片 bitmap = decodeSampledBitmapFromResource(drawableIds[0], dstWidth, measuredHeight); srcRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight()); dstRect.set(0, 0, dstWidth, measuredHeight); canvas.drawBitmap(bitmap, srcRect, dstRect, bitmapPaint); //繪制分割線 linePaint.setColor(Color.WHITE); canvas.drawLine(dstWidth, 0, dstWidth+lineWidth, getMeasuredHeight(), linePaint); //繪制第二張圖片 bitmap = decodeSampledBitmapFromResource(drawableIds[1], dstWidth, getMeasuredHeight()); srcRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight()); dstRect.set(dstWidth+lineWidth, 0, getMeasuredWidth(), getMeasuredHeight()); canvas.drawBitmap(bitmap, srcRect, dstRect, bitmapPaint); }
三張圖片和四張圖片的情況,類似,只要按著上面的邏輯來,就差不多。是不是感覺很簡單?確實,其實相當一部分的自定義View,不像想象中的那麼復雜。而且這次分享的這一個效果,其重點也並不在繪制的邏輯上面,而是在於從一個來源(或者說叫做大小,尺寸)不確定的圖片上面,根據你自己需要的大小,加載、裁剪出合適尺寸的圖片,同時還要考慮內存占用,不要發生OOM。
解決這個問題的思路是:
1. 先將”BitmapFactory.Options”對象的”inJustDecodeBounds”屬性設置為true,這樣子能獲取圖片相關信息。
2. 根據我們所需要的最終尺寸,以及圖片原來信息,計算以及設置壓縮比例。
3. 設置好壓縮比例,將剛才的那個屬性設置為false,將一個“比較小的”bitmap給加載進來。
4. 通過”Bitmap.createScaledBitmap(Bitmap, int , int , boolean)”方法,得到最終我們要的尺寸的Bitmap。
參考片段代碼如下:
/** * 從Resources中加載圖片 * @param resId 圖片資源 * @param reqWidth 目標寬度 * @param reqHeight 目標高度 * @return */ private Bitmap decodeSampledBitmapFromResource(int resId, int reqWidth, int reqHeight) { final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; // 設置成了true,不占用內存,只獲取bitmap寬高 BitmapFactory.decodeResource(getResources(), resId, options); // 讀取圖片長寬,目的是得到圖片的寬高 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // 調用上面定義的方法計算inSampleSize值 // 使用獲取到的inSampleSize值再次解析圖片 options.inJustDecodeBounds = false; Bitmap src = BitmapFactory.decodeResource(getResources(), resId, options); // 載入一個稍大的縮略圖 return createScaleBitmap(src, reqWidth, reqHeight, options.inSampleSize); // 通過得到的bitmap,進一步得到目標大小的縮略圖 } /** * 計算圖片的壓縮比率 * @param options 參數 * @param reqWidth 目標的寬度 * @param reqHeight 目標的高度 * @return inSampleSize 壓縮比率 */ private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { //源圖片的高度和寬度 final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; } } return inSampleSize; } /** * 通過傳入的bitmap,進行壓縮,得到符合標准的bitmap * @param src 原圖片Bitmap * @param dstWidth 目標寬度 * @param dstHeight 目標高度 * @return 壓縮後的圖片Bitmap */ private static Bitmap createScaleBitmap(Bitmap src, int dstWidth, int dstHeight, int inSampleSize) { // 如果是放大圖片,filter決定是否平滑,如果是縮小圖片,filter無影響,我們這裡是縮小圖片,所以直接設置為false Bitmap dst = Bitmap.createScaledBitmap(src, dstWidth, dstHeight, false); //如果圖片有縮放,回收原來的圖片 if (src != dst) src.recycle(); return dst; }
項目源碼:
https://github.com/JaffarOu/SimpleCustomView
這個項目裡面集合了好幾個自定義View,本文所對應的View類名叫做”MergePictureView”。
ImageView家族的繼承關系如圖:ImageView繼承自View組件,它的主要功能室顯示圖片,還可以顯示Drawable對象。ImageView直接子類是Image
Bitmap bitmap1; Bitmap bitmap2; Bitmap bitmap3 = Bitmap.createBitmap(bitmap1.getWidth
Android中Service的詳細解釋與使用:概念:(1).Service可以說是一個在後台運行的Activity。它不是一個單獨的進程,它只需要應用告訴它要在後台做什
我們先說一下思路,在android系統中就自帶了圖片剪切的應用,所以,我們只需要將我們獲取到的相片傳給圖片剪切應用,再將剪切好的相片返回到我們自己的界面顯示就ok了在開發