Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android自定義View分享——仿微信朋友圈圖片合並效果

Android自定義View分享——仿微信朋友圈圖片合並效果

編輯:關於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之類網絡框架下載圖片,請搞清楚這些框架下載圖片之後對圖片所做的事情,筆者曾經在實際項目裡,使用一些框架導致數據錯亂,需要另外進行其他調試。

一張圖片時的代碼片段
decodeSampledBitmapFromResource()方法是個自定義的內部方法,根據實際需要尺寸加載圖片,用來防止內存耗盡,這個將在稍後展開來講。
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);
}
兩張圖片時的代碼片段
兩張圖片,圖片的最終寬度就成了控件寬度減去白色分隔線的寬度,再除以2。
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。

加載尺寸不確定的Bitmap時的內存占用問題

解決這個問題的思路是:
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”。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved