繼承 ImageView,利用 PorterDuffXfermode 的 `SRC_IN` 模式,以圓角矩形作為遮罩,實現圖片圓角顯示,支援 XML 屬性配置圓角半徑。
RectF 與 Paint。public class RoundedImageView extends ImageView {
private Paint paint;
private RectF rectF;
private float radius = 40f; // 預設圓角半徑
private PorterDuffXfermode xfermode;
public RoundedImageView(Context context) {
this(context, null);
}
public RoundedImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RoundedImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
// 可從 attrs 讀取自訂屬性 radius
}
private void init() {
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
rectF = new RectF();
xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
}
@Override
protected void onDraw(Canvas canvas) {
// 開啟離屏圖層
int sc = canvas.saveLayer(0, 0, getWidth(), getHeight(), null,
Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG |
Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG |
Canvas.CLIP_TO_LAYER_SAVE_FLAG);
rectF.set(0, 0, getWidth(), getHeight());
// 1. 繪製圓角矩形遮罩
canvas.drawRoundRect(rectF, radius, radius, paint);
// 2. 設定 Xfermode:只保留遮罩與圖片重疊部分
paint.setXfermode(xfermode);
// 3. 繪製原始圖片(super.onDraw 內部會畫 drawable)
super.onDraw(canvas);
// 4. 恢復
paint.setXfermode(null);
canvas.restoreToCount(sc);
}
/** 供外部動態設定圓角 */
public void setRadius(float radius) {
this.radius = radius;
invalidate();
}
}
<com.example.RoundedImageView
android:id="@+id/img_avatar"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/avatar"
android:scaleType="centerCrop" />
saveLayer 較耗效能,避免在列表項目等高頻刷新場景大量使用;可考慮 `BitmapShader` 或 `Canvas.clipPath` 替代。| 方案 | 優點 | 缺點 |
|---|---|---|
| PorterDuffXfermode | 支援任意形狀遮罩、邊框、陰影 | 需軟體繪製、效能較差 |
| BitmapShader | 硬體加速友善、效能佳 | 不支援描邊、陰影 |
| Canvas.clipPath | 簡單、硬體加速支援 | 抗鋸齒效果視裝置而定 |
| CardView + 防裁剪 | XML 宣告、無代碼 | 僅限矩形圓角、無法自訂形狀 |