一、 特點(diǎn)
- 基于AppCompatImageView擴(kuò)展
- 支持圓角起趾、圓形顯示
- 可繪制邊框诗舰,圓形時(shí)可繪制內(nèi)外兩層邊框
- 支持邊框不覆蓋圖片
- 可繪制遮罩
- ......
二、基本原理
我們要實(shí)現(xiàn)的圖片控件繼承自AppCompatImageView
阳掐,它是ImageView
的子類始衅,但提供了更好的兼容性冷蚂,我們?cè)诖嘶A(chǔ)上添加了若干自定義的屬性和方法以實(shí)現(xiàn)最終的 NiceImageView:
public class NiceImageView extends AppCompatImageView {
......
}
要實(shí)圓角或者圓形的顯示效果,就是對(duì)圖片顯示的內(nèi)容區(qū)域進(jìn)行“裁剪”汛闸,只顯示指定的區(qū)域即可蝙茶。如何做呢?
一種比較直接的辦法是這樣的诸老,由于圖片是被繪制在畫(huà)布上的隆夯,所以用canvas
的 clipPath()
方法先將畫(huà)布裁剪成指定形狀,這樣就能讓圖片按指定形狀顯示了别伏,重新draw()
方法即可:
@Override
public void draw(Canvas canvas) {
canvas.save();
canvas.clipPath(path);
super.draw(canvas);
canvas.restore();
}
這樣使用src
蹄衷、background
屬性給ImageView設(shè)置顯示的圖片都能達(dá)到預(yù)期的顯示效果。但是由于clipPath()
方法不支持抗鋸齒厘肮,圖片邊緣會(huì)有明顯的毛糙感愧口,體驗(yàn)并不理想,所以需要尋找其它方法类茂。
另一種方法是使用圖像的 Alpha 合成模式耍属,即
PorterDuff 來(lái)實(shí)現(xiàn),官方文檔巩检。這里我們使用其中的DST_IN模式厚骗。整個(gè)過(guò)程就是先繪制目標(biāo)圖像,也就是圖片兢哭;再繪制原圖像领舰,即一個(gè)圓角矩形或者圓形,這樣最終目標(biāo)圖像只顯示和原圖像重合的區(qū)域迟螺。
xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
@Override
protected void onDraw(Canvas canvas) {
// 使用離屏緩存冲秽,新建一個(gè)srcRectF區(qū)域大小的圖層
canvas.saveLayer(srcRectF, null, Canvas.ALL_SAVE_FLAG);
// ImageView自身的繪制流程,即繪制圖片
super.onDraw(canvas);
// 給path添加一個(gè)圓角矩形或者圓形
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);
// 畫(huà)筆為填充模式
paint.setStyle(Paint.Style.FILL);
// 設(shè)置混合模式
paint.setXfermode(xfermode);
// 繪制path
canvas.drawPath(path, paint);
// 清除Xfermode
paint.setXfermode(null);
// 恢復(fù)畫(huà)布狀態(tài)
canvas.restore();
}
到這里就實(shí)現(xiàn)了顯示為圓角或者圓形了煮仇。但是需要通過(guò)src
屬性或者對(duì)應(yīng)的方法來(lái)設(shè)置圖片劳跃,否則不能達(dá)到預(yù)期效果。
三浙垫、繪制邊框
繪制邊框就相對(duì)容易理解了刨仑,只需要繪制一個(gè)指定樣式的圓角矩形或者圓形即可:
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();
// 設(shè)置畫(huà)筆為描邊模式
paint.setStyle(Paint.Style.STROKE);
// 描邊寬度
paint.setStrokeWidth(borderWidth);
// 描邊顏色
paint.setColor(borderColor);
}
當(dāng)圖片顯示為圓形時(shí),還可以繪制一個(gè)內(nèi)邊框夹姥,但圓角矩形的話由于圓角大小的問(wèn)題杉武,目前只能設(shè)置一個(gè)邊框咯。
但是有個(gè)問(wèn)題辙售,繪制的邊框會(huì)覆蓋在圖片上轻抱,如果邊框太寬會(huì)導(dǎo)致圖片的可見(jiàn)區(qū)域變小了,影像顯示效果旦部,像這樣祈搜,左下角的花盆不見(jiàn)了:
那么如何讓邊框不覆蓋在圖片上呢较店?可以在 Alpha 合成繪制前先將畫(huà)布縮小一定比例,最后再繪制邊框容燕,這樣問(wèn)題就解決了梁呈。
@Override
protected void onDraw(Canvas canvas) {
canvas.saveLayer(srcRectF, null, Canvas.ALL_SAVE_FLAG);
// 縮小畫(huà)布
if (!isCoverSrc) {
float sx = 1.0f * (width - 2 * borderWidth - 2 * innerBorderWidth) / width;
float sy = 1.0f * (height - 2 * borderWidth - 2 * innerBorderWidth) / height;
// 縮小畫(huà)布,使圖片內(nèi)容不被border蘸秘、padding覆蓋
canvas.scale(sx, sy, width / 2.0f, height / 2.0f);
}
......
canvas.restore();
// 繪制邊框
drawBorders(canvas);
}
縮放后的ImageView顯示區(qū)域的寬高就是原寬官卡、高分別減去2倍的邊框?qū)挾龋@樣縮小的比例也就顯而易見(jiàn)了醋虏。效果如下寻咒,左下角的花盆出來(lái)了:
四、繪制遮罩
遮罩可以理解為一層帶透明度的顏色颈嚼,遮罩默認(rèn)不繪制毛秘,當(dāng)制定了遮罩顏色時(shí)才會(huì)繪制,實(shí)現(xiàn)很簡(jiǎn)單:
@Override
protected void onDraw(Canvas canvas) {
......
// 繪制遮罩
if (maskColor != 0) {
paint.setColor(maskColor);
canvas.drawPath(path, paint);
}
canvas.restore();
drawBorders(canvas);
}
例如加一個(gè)透明度30%的紅色遮罩后的效果:
核心的實(shí)現(xiàn)邏輯就這些了粘舟,剩下的就是自定義屬性和方法了奈嘿,有興趣的可以看源碼系羞,都很簡(jiǎn)單,希望對(duì)你有所幫助吧轻庆!
更多細(xì)節(jié)及用法見(jiàn)GitHub:https://github.com/SheHuan/NiceImageView
五旬薯、其它
如果你需要實(shí)現(xiàn)類似釘釘?shù)膱A形組合頭像晰骑,例如:
可以先生成對(duì)應(yīng)的Bitmap,并用圓形的 NiceImageView 顯示即可绊序。如何生成組合Bitmap可以參考這里:CombineBitmap硕舆。