最近被人問起圓角圖片的實(shí)現(xiàn),花了一點(diǎn)時(shí)間鼓搗了下脓规,下面簡單分享下。
完整例子: RoundImage
先上效果圖
下面為主要源碼,實(shí)現(xiàn)了 Picasso 中的 Transformation 接口凉敲。
public class RoundCornersTransformation implements Transformation {
private float mRadius;
private DrawCornerImage mDrawCornerImage;
private Paint mPaint;
public RoundCornersTransformation(float radius, DrawCornerImage drawCornerImage) {
mRadius = radius;
mDrawCornerImage = drawCornerImage;
mPaint = new Paint();
mPaint.setAntiAlias(true);
}
@Override
public Bitmap transform(Bitmap source) {//這里為主要邏輯,原理可以套用在其他地方寺旺,比如 Glide
int width = source.getWidth();
int height = source.getHeight();
Bitmap newSource = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(newSource);
mPaint.setShader(new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
source.recycle();
//畫圓角的邏輯代碼爷抓,交給 DrawCornerImage 接口的具體實(shí)現(xiàn)類
mDrawCornerImage.drawCornerImage(canvas, mPaint, mRadius, width, height);
return newSource;
}
@Override
public String key() {
return RoundCornersTransformation.class.toString();
}
}
定義 DrawCornerImage 接口將變化的部分抽離出來
public interface DrawCornerImage {
void drawCornerImage(Canvas canvas, Paint paint,float radius, int right, int bottom);
}
DrawTopCornerImage 為 DrawCornerImage 的一個(gè)實(shí)現(xiàn)類,負(fù)責(zé)具體的圓角邏輯:只有頂部為圓角
public class DrawTopCornerImage implements DrawCornerImage {
@Override
public void drawCornerImage(Canvas canvas, Paint paint, float radius, int right, int bottom) {
//繪制一個(gè)全部圓角的矩形區(qū)域長寬分別為 right 和 bottom
canvas.drawRoundRect(new RectF(0, 0, right, bottom), radius, radius, paint);
//繪制一個(gè)矩形長寬分別為 right 和 bottom-radius阻塑,相當(dāng)于底部和上面對齊而高度差個(gè) radius, 和上面所繪制的并集蓝撇,即為圖片的可見區(qū)域。并集即為上部為圓角而底部是直角的一個(gè)區(qū)域
canvas.drawRect(new RectF(0, radius, right, bottom), paint);
}
}
原理簡單來講叮姑,就是所繪制區(qū)域的并集為可見區(qū)域唉地,注意是并集不是交集。
使用起來還是比較方便的:
Picasso.with(this)
.load(R.drawable.ic_girl)
.transform(new RoundCornersTransformation(corner, new DrawTopLeftCornerImage()))// here change draw strategy:DrawAllCornerImage ,DrawBottomCornerImage etc.
.into(iv);
其他的圓角邏輯可以自行發(fā)揮传透,上面的原理不局限于 Picasso 完全也可以用在 Glide 或則其他地方耘沼,結(jié)合圖片庫的封裝可以對上面繼續(xù)進(jìn)行一次封裝。
有一點(diǎn)提一下如果你的 ImageView 有用 android:scaleType="centerCrop" 屬性朱盐,可能上面方法就有點(diǎn)不合適了群嗤,centerCrop 屬性會截取圖片的中心區(qū)域展示很可能圓角就不在展示范圍了。但是大多場景 UI 給出的設(shè)計(jì)尺寸和圖片比例應(yīng)該是一致的兵琳,上面的適用范圍還是很大的狂秘。
如果你想達(dá)到 centerCrop 屬性的效果骇径,也不是不可以,只是不適合封裝在 Picasso 的內(nèi)部邏輯中了者春。因?yàn)槲覀冃枰?ImageView 的寬高破衔,這其實(shí)更合適封裝成一個(gè)自定義 View。
下面還是直接以上面的代碼钱烟,寫個(gè)示例晰筛,并不合適使用在實(shí)際項(xiàng)目中,僅為了說明原理拴袭。
@Override
public Bitmap transform(Bitmap source) {
float ivWidth = 600;//600 在我測試機(jī)中 ImageView 的像素大小
float ivHeight = 600;//600 在我測試機(jī)中 ImageView 的像素大小
int width = source.getWidth();
int height = source.getHeight();
//按照 ImageView 的大小創(chuàng)建一個(gè) Bitmap
Bitmap newSource = Bitmap.createBitmap(ivWidth , ivHeight , Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(newSource);
BitmapShader bitmapShader = new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
float scale;
// ImageView 和原圖片的寬高比读第,取其大值為了放大后能夠完全覆蓋 ImageView 的大小
scale = Math.max(ivWidth / width, ivHeight / height);
//利用 Matrix 矩陣進(jìn)行縮放和居中操作
mShaderMatrix.reset();
mShaderMatrix.setScale(scale, scale);
//將放大后的圖片向上移動,達(dá)到中心位置(實(shí)際情況根據(jù)圖片的各種大小拥刻,有可能也會在 x 軸方向進(jìn)行移動怜瞒,這里僅作示例演示)
mShaderMatrix.postTranslate(0, -(height * scale - ivHeight) / 2.0f);
bitmapShader.setLocalMatrix(mShaderMatrix);
mPaint.setShader(bitmapShader);
source.recycle();
mDrawCornerImage.drawCornerImage(canvas, mPaint, mRadius, ivWidth, ivHeight);
return newSource;
}
下圖為和開源控件 RoundedImageView 的效果對比
大家閱讀它的源碼會發(fā)現(xiàn)原理是一樣的,大家有什么其他需要可以直接使用
RoundedImageView