介绍
源码参考文章,RoundedImageView是用来对图片进行裁剪,可以在代码块中实现,也可以在布局xml中实现。
github源码地址:https://github.com/vinc3m1/RoundedImageView
作者对这个开源框架的评价就是
There are many ways to create rounded corners in android, but this is the fastest and best one that I know of because it:
- does not create a copy of the original bitmap
- does not use a clipPath which is not hardware accelerated and not anti-aliased.
- does not use setXfermode to clip the bitmap and draw twice to the canvas.
第一是无需对原始的bitmap进行复制,第二是不需硬件上的加速和反锯齿,第三是没有使用用Xfermode,所以需要使用画布处理两次
应用
第一个当然是对库的支持
compile 'com.makeramen:roundedimageview:2.3.0'
在xml文件中引用:
<com.makeramen.roundedimageview.RoundedImageView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/imageView1"
android:src="@drawable/photo1"
android:scaleType="fitCenter"
app:riv_corner_radius="30dip"
app:riv_border_width="2dip"
app:riv_border_color="#333333"
app:riv_mutate_background="true"
app:riv_tile_mode="repeat"
app:riv_oval="true" />
在代码中引用:
RoundedImageView riv = new RoundedImageView(context);
riv.setScaleType(ScaleType.CENTER_CROP);
riv.setCornerRadius((float) 10);
riv.setBorderWidth((float) 2);
riv.setBorderColor(Color.DKGRAY);
riv.mutateBackground(true);
riv.setImageDrawable(drawable);
riv.setBackground(backgroundDrawable);
riv.setOval(true);
riv.setTileModeX(Shader.TileMode.REPEAT);
riv.setTileModeY(Shader.TileMode.REPEAT);
可以参考下别人详细的实现过程http://blog.csdn.net/aaawqqq/article/details/38057145
开始
RoundedImageView主要的核心是在RoundedDrawer所以核心是对于RoundedDrawer的分析
一、Drawable转化为圆角的Drawable
1、构造方法
public RoundedDrawable(Bitmap bitmap) {
mBitmap = bitmap;
mBitmapWidth = bitmap.getWidth();
mBitmapHeight = bitmap.getHeight();
mBitmapRect.set(0, 0, mBitmapWidth, mBitmapHeight);
mBitmapPaint = new Paint();
mBitmapPaint.setStyle(Paint.Style.FILL);
mBitmapPaint.setAntiAlias(true);
mBorderPaint = new Paint();
mBorderPaint.setStyle(Paint.Style.STROKE);
mBorderPaint.setAntiAlias(true);
mBorderPaint.setColor(mBorderColor.getColorForState(getState(), DEFAULT_BORDER_COLOR));
mBorderPaint.setStrokeWidth(mBorderWidth);
}
这里就能用来解释为什么上面作者说的第三点没使用setXfermode所以需要使用画布画两次,如果使用setXfermode进行绘制就只需要一次。
2、fromDrawable方法
public static Drawable fromDrawable(Drawable drawable) {
if (drawable != null) {
if (drawable instanceof RoundedDrawable) {
// just return if it's already a RoundedDrawable
return drawable;
} else if (drawable instanceof LayerDrawable) {
LayerDrawable ld = (LayerDrawable) drawable;
int num = ld.getNumberOfLayers();
// loop through layers to and change to RoundedDrawables if possible
for (int i = 0; i < num; i++) {
Drawable d = ld.getDrawable(i);
ld.setDrawableByLayerId(ld.getId(i), fromDrawable(d));
}
return ld;
}
// try to get a bitmap from the drawable and
Bitmap bm = drawableToBitmap(drawable);
if (bm != null) {
return new RoundedDrawable(bm);
}
}
return drawable;
}
这个方法中我们可以看到如果drawable本身就是RoundedDrawable,那么直接返回,如果不是就每一个LayoutDrawable的item改造成RoundedDrawable,我们可以看到其中使用fromDrawable的递归调用改造。
后将drawable转成bitmap后直接返回RoundedDrawable
3、生成圆角Draw
@Override
public void draw(@NonNull Canvas canvas) {
if (mRebuildShader) {
BitmapShader bitmapShader = new BitmapShader(mBitmap, mTileModeX, mTileModeY);
if (mTileModeX == Shader.TileMode.CLAMP && mTileModeY == Shader.TileMode.CLAMP) {
bitmapShader.setLocalMatrix(mShaderMatrix);
}
mBitmapPaint.setShader(bitmapShader);
mRebuildShader = false;
}
if (mOval) {
if (mBorderWidth > 0) {
canvas.drawOval(mDrawableRect, mBitmapPaint);
canvas.drawOval(mBorderRect, mBorderPaint);
} else {
canvas.drawOval(mDrawableRect, mBitmapPaint);
}
} else {
if (any(mCornersRounded)) {
float radius = mCornerRadius;
if (mBorderWidth > 0) {
canvas.drawRoundRect(mDrawableRect, radius, radius, mBitmapPaint);
canvas.drawRoundRect(mBorderRect, radius, radius, mBorderPaint);
redrawBitmapForSquareCorners(canvas);
redrawBorderForSquareCorners(canvas);
} else {
canvas.drawRoundRect(mDrawableRect, radius, radius, mBitmapPaint);
redrawBitmapForSquareCorners(canvas);
}
} else {
canvas.drawRect(mDrawableRect, mBitmapPaint);
if (mBorderWidth > 0) {
canvas.drawRect(mBorderRect, mBorderPaint);
}
}
}
}
我们先看看第4行代码,这里用到BitmapShader,这里表明,圆角是通过BitmapShader实现的。接下来我们看到BitmapShader调用了setLocalMatrix方法,最后mBitmapPaint.setShader(bitmapShader);将bitmapShader设置给mBitmapPaint。
我们可以看到里面有一个判断就是判断是否要求剪裁为圆角,如果是那么就就对其四周都剪裁成圆角,如果不是的话就使用
redrawBitmapForSquareCorners(canvas);
方法,将已经是圆角的恢复成直角,有兴趣可以看下源码,他是绘制一个正方形填充圆角的部分。
4、ScaleType
public RoundedDrawable setScaleType(ScaleType scaleType) {
if (scaleType == null) {
scaleType = ScaleType.FIT_CENTER;
}
if (mScaleType != scaleType) {
mScaleType = scaleType;
updateShaderMatrix();
}
return this;
}
这里就是对于ScaleType的支持,相关的操作在updateShaderMatrix()方法里面,就fitCenter等等,有兴趣可以进去看看
二、src属性
1、重写setImageDrawable,setImageBitmap和setImageResource
@Override
public void setImageDrawable(Drawable drawable) {
mResource = 0;
mDrawable = RoundedDrawable.fromDrawable(drawable);
updateDrawableAttrs();
super.setImageDrawable(mDrawable);
}
@Override
public void setImageBitmap(Bitmap bm) {
mResource = 0;
mDrawable = RoundedDrawable.fromBitmap(bm);
updateDrawableAttrs();
super.setImageDrawable(mDrawable);
}
@Override
public void setImageResource(@DrawableRes int resId) {
if (mResource != resId) {
mResource = resId;
mDrawable = resolveResource();
updateDrawableAttrs();
super.setImageDrawable(mDrawable);
}
}
这两个方法都是获取Drawable之后进入UpdateDrawableAttrs(),那我们就进去看看里面是什么
private void updateDrawableAttrs() {
updateAttrs(mDrawable);
}
继续进入updateAttrs
private void updateAttrs(Drawable drawable) {
if (drawable == null) { return; }
if (drawable instanceof RoundedDrawable) {
((RoundedDrawable) drawable)
.setScaleType(mScaleType)
.setBorderWidth(mBorderWidth)
.setBorderColor(mBorderColor)
.setOval(mIsOval)
.setTileModeX(mTileModeX)
.setTileModeY(mTileModeY);
if (mCornerRadii != null) {
((RoundedDrawable) drawable).setCornerRadius(
mCornerRadii[Corner.TOP_LEFT],
mCornerRadii[Corner.TOP_RIGHT],
mCornerRadii[Corner.BOTTOM_RIGHT],
mCornerRadii[Corner.BOTTOM_LEFT]);
}
applyColorMod();
} else if (drawable instanceof LayerDrawable) {
// loop through layers to and set drawable attrs
LayerDrawable ld = ((LayerDrawable) drawable);
for (int i = 0, layers = ld.getNumberOfLayers(); i < layers; i++) {
updateAttrs(ld.getDrawable(i));
}
}
}
这部分代码跟第一点的fromDrawable()方法有点类似,判断如果是RoundedDrawable那我们就设置相关属性,如果不是那就把LayoutDrawable重新提取传去updateAttrs(),到这里的圆角属性就可以实现
三、background属性
@Override
public void setBackground(Drawable background) {
setBackgroundDrawable(background);
}
@Override
@Deprecated
public void setBackgroundDrawable(Drawable background) {
mBackgroundDrawable = background;
updateBackgroundDrawableAttrs(true);
super.setBackgroundDrawable(mBackgroundDrawable);
}
这两个函数都用到了setBackgroundDrawable(),那我们进去看看
private void updateBackgroundDrawableAttrs(boolean convert) {
if (mMutateBackground) {
if (convert) {
mBackgroundDrawable = RoundedDrawable.fromDrawable(mBackgroundDrawable);
}
updateAttrs(mBackgroundDrawable);
}
}
这里面涉及到mMutateBackground属性,如果mMutateBackground为true,并且传入的参数也为true的话,会调用RoundedDrawable的方法把mBackgroundDrawable转化为圆角,最后通过之前讲的updateAttrs(Drawable)方法实现更新mBackgroundDrawable有关RoundedDrawable的属性。
这里就是设置背景是否设置为圆角,可以在xml里面设置也可以在代码里面设置
xml里面设置app:riv_mutate_background="true"或者再代码里面riv.mutateBackground(true)。