Android 圓角、圓形 ImageView 實現

SheHuan發表於2018-06-25
Android 圓角、圓形 ImageView 實現
Android 圓角、圓形 ImageView 實現
Android 圓角、圓形 ImageView 實現
Android 圓角、圓形 ImageView 實現
Android 圓角、圓形 ImageView 實現
Android 圓角、圓形 ImageView 實現

一、 特點

  • 基於AppCompatImageView擴充套件
  • 支援圓角、圓形顯示
  • 可繪製邊框,圓形時可繪製內外兩層邊框
  • 支援邊框不覆蓋圖片
  • 可繪製遮罩
  • ......

二、基本原理

我們要實現的圖片控制元件繼承自AppCompatImageView,它是ImageView的子類,但提供了更好的相容性,我們在此基礎上新增了若干自定義的屬性和方法以實現最終的 NiceImageView

public class NiceImageView extends AppCompatImageView {
    ......
}
複製程式碼

要實圓角或者圓形的顯示效果,就是對圖片顯示的內容區域進行“裁剪”,只顯示指定的區域即可。如何做呢?

一種比較直接的辦法是這樣的,由於圖片是被繪製在畫布上的,所以用canvasclipPath()方法先將畫布裁剪成指定形狀,這樣就能讓圖片按指定形狀顯示了,重新draw()方法即可:

    @Override
    public void draw(Canvas canvas) {
        canvas.save();
        canvas.clipPath(path);
        super.draw(canvas);
        canvas.restore();
    }
複製程式碼

這樣使用srcbackground屬性給ImageView設定顯示的圖片都能達到預期的顯示效果。但是由於clipPath()方法不支援抗鋸齒,圖片邊緣會有明顯的毛糙感,體驗並不理想,所以需要尋找其它方法。

另一種方法是使用影象的 Alpha 合成模式,即 PorterDuff 來實現,官方文件。這裡我們使用其中的DST_IN模式。整個過程就是先繪製目標影象,也就是圖片;再繪製原影象,即一個圓角矩形或者圓形,這樣最終目標影象只顯示和原影象重合的區域。

xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
複製程式碼
    @Override
    protected void onDraw(Canvas canvas) {
        // 使用離屏快取,新建一個srcRectF區域大小的圖層
        canvas.saveLayer(srcRectF, null, Canvas.ALL_SAVE_FLAG);
        // ImageView自身的繪製流程,即繪製圖片
        super.onDraw(canvas);
        // 給path新增一個圓角矩形或者圓形
        if (isCircle) {
            path.addCircle(width / 2.0f, height / 2.0f, radius, Path.Direction.CCW);
        } else {
            path.addRoundRect(srcRectF, srcRadii, Path.Direction.CCW);
        }
        paint.setAntiAlias(true);
        // 畫筆為填充模式
        paint.setStyle(Paint.Style.FILL);
        // 設定混合模式
        paint.setXfermode(xfermode);
        // 繪製path
        canvas.drawPath(path, paint);
        // 清除Xfermode
        paint.setXfermode(null);
        // 恢復畫布狀態
        canvas.restore();
    }
複製程式碼

到這裡就實現了顯示為圓角或者圓形了。但是需要通過src屬性或者對應的方法來設定圖片,否則不能達到預期效果。

三、繪製邊框

繪製邊框就相對容易理解了,只需要繪製一個指定樣式的圓角矩形或者圓形即可:

    private void drawBorders(Canvas canvas) {
        if (isCircle) {
            if (borderWidth > 0) {
                drawCircleBorder(canvas, borderWidth, borderColor, radius - borderWidth / 2.0f);
            }
            if (innerBorderWidth > 0) {
                drawCircleBorder(canvas, innerBorderWidth, innerBorderColor, radius - borderWidth - innerBorderWidth / 2.0f);
            }
        } else {
            if (borderWidth > 0) {
                drawRectFBorder(canvas, borderWidth, borderColor, borderRectF, borderRadii);
            }
        }
    }

    private void drawCircleBorder(Canvas canvas, int borderWidth, int borderColor, float radius) {
        initBorderPaint(borderWidth, borderColor);
        path.addCircle(width / 2.0f, height / 2.0f, radius, Path.Direction.CCW);
        canvas.drawPath(path, paint);
    }

    private void drawRectFBorder(Canvas canvas, int borderWidth, int borderColor, RectF rectF, float[] radii) {
        initBorderPaint(borderWidth, borderColor);
        path.addRoundRect(rectF, radii, Path.Direction.CCW);
        canvas.drawPath(path, paint);
    }

    private void initBorderPaint(int borderWidth, int borderColor) {
        path.reset();
        // 設定畫筆為描邊模式
        paint.setStyle(Paint.Style.STROKE);
        // 描邊寬度
        paint.setStrokeWidth(borderWidth);
        // 描邊顏色
        paint.setColor(borderColor);
    }
複製程式碼

當圖片顯示為圓形時,還可以繪製一個內邊框,但圓角矩形的話由於圓角大小的問題,目前只能設定一個邊框咯。

但是有個問題,繪製的邊框會覆蓋在圖片上,如果邊框太寬會導致圖片的可見區域變小了,影像顯示效果,像這樣,左下角的花盆不見了:

Android 圓角、圓形 ImageView 實現
那麼如何讓邊框不覆蓋在圖片上呢?可以在 Alpha 合成繪製前先將畫布縮小一定比例,最後再繪製邊框,這樣問題就解決了。

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.saveLayer(srcRectF, null, Canvas.ALL_SAVE_FLAG);
        // 縮小畫布
        if (!isCoverSrc) {
            float sx = 1.0f * (width - 2 * borderWidth - 2 * innerBorderWidth) / width;
            float sy = 1.0f * (height - 2 * borderWidth - 2 * innerBorderWidth) / height;
            // 縮小畫布,使圖片內容不被border、padding覆蓋
            canvas.scale(sx, sy, width / 2.0f, height / 2.0f);
        }
        ......
        canvas.restore();
        // 繪製邊框
        drawBorders(canvas);
    }
複製程式碼

縮放後的ImageView顯示區域的寬高就是原寬、高分別減去2倍的邊框寬度,這樣縮小的比例也就顯而易見了。效果如下,左下角的花盆出來了:

Android 圓角、圓形 ImageView 實現

四、繪製遮罩

遮罩可以理解為一層帶透明度的顏色,遮罩預設不繪製,當制定了遮罩顏色時才會繪製,實現很簡單:

    @Override
    protected void onDraw(Canvas canvas) {
        ......      
        // 繪製遮罩
        if (maskColor != 0) {
            paint.setColor(maskColor);
            canvas.drawPath(path, paint);
        }
        canvas.restore();
        drawBorders(canvas);
    }
複製程式碼

例如加一個透明度30%的紅色遮罩後的效果:

Android 圓角、圓形 ImageView 實現

核心的實現邏輯就這些了,剩下的就是自定義屬性和方法了,有興趣的可以看原始碼,都很簡單,希望對你有所幫助吧!

更多細節及用法見GitHub:github.com/Othershe/Ni…

五、其它

如果你需要實現類似釘釘的圓形組合頭像,例如:

Android 圓角、圓形 ImageView 實現
可以先生成對應的Bitmap,並用圓形的 NiceImageView 顯示即可。如何生成組合Bitmap可以參考這裡:CombineBitmap

相關文章