您的位置:首页 > 移动开发 > Android开发

Android自定义圆角圆形图片

2018-03-07 11:44 417 查看

转载请注明出处:http://blog.csdn.net/binbinqq86/article/details/79463977

说起Android里面的自定义圆角圆形图片,已经算是老生常谈的话题了,之前一直使用别人的,最近使用的时候发现自己居然没有一个这样属于自己的工具库,实在遗憾,毕竟还是自己的东西用起来最顺手,所以就打造了一个,先来看看效果:



怎么样,还不错吧~支持各种图案,边框,各种圆角。其中原理也是很简单的,无非就是canvas知识的应用。接下来我们一个一个形状来看,首先是圆形,这个应该是最简单的了,直接使用canvas的drawCircle来绘制一个圆形就搞定了,看代码:

float r = hasBorder ? width / 2f - borderWidth : width / 2f;
canvas.drawCircle(width / 2f, height / 2f, r, mPaintDrawable);


其中的r就是圆的半径,这里我们用一个变量hasBorder 来区分是否绘制边框(边框后面细说),drawCircle的前两个参数就是圆心坐标,最后一个参数则是画笔,我们的图片呢???确定这样就能画出来圆形图片???其实图片被我们封装在笔刷里面了:BitmapShader。这个类的使用很简单,官方文档是这样解释的:

Shader used to draw a bitmap as a texture.


就是使用特定的图片来作为纹理使用。这里就不再详细解释这个类的使用了,不懂的同学可以参考文章最后的链接。其实除了使用BitmapShader还有另外一种方案,就是PorterDuffXfermode,感兴趣的可以参考我之前的一篇文章:http://blog.csdn.net/binbinqq86/article/details/78329238,里面讲述了另外一种实现圆形图片的方案。

下面来看一下获取Bitmap的方法:

/**
* 获取imageview设置的图片(针对大图,此时必须已经处理过了,否则会造成内存溢出)
* <p>
* 获取bitmap:
* 1、如果设置了src为图片则返回该图片,
* 2、如果设置了src为颜色值则返回颜色值,
* 3、如果没有设置src,则返回默认颜色值(未设置则为透明)
*
* @param drawable
* @return
*/
private Bitmap getBitmap(Drawable drawable) {
Bitmap bitmap = null;
if (drawable instanceof BitmapDrawable) {
bitmap = ((BitmapDrawable) drawable).getBitmap();
} else if (drawable instanceof ColorDrawable) {
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bitmap);
int color = ((ColorDrawable) drawable).getColor();
c.drawARGB(Color.alpha(color), Color.red(color), Color.green(color), Color.blue(color));
} else {
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bitmap);
c.drawARGB(Color.alpha(defaultColor), Color.red(defaultColor), Color.green(defaultColor), Color.blue(defaultColor));
}
if (isBlur) {
//高斯模糊
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
try {
bitmap = RSBlur.blur(getContext(), bitmap, (int) blurRadius);
} catch (Exception e) {
bitmap = FastBlur.blur(bitmap, (int) blurRadius, true);
}
} else {
bitmap = FastBlur.blur(bitmap, (int) blurRadius, true);
}
}
return bitmap;
}


这里就是获取imageview设置的图片,然后就可以随意绘制我们想要的效果了,这里我们取图片的中间区域进行绘制,可以防止图片跟视图大小不一样(前提是我们已经根据imageView的宽高去缩放过图片了,这里处理的只是绘制部分,类似imageView的centerCrop),看代码:

/**
* 处理图片大于或者小于控件的情况
* (不是针对大图内存溢出的处理,此处的处理只是为了让图片居中绘制——centerCrop:参照imageView的处理)
*
* @param bitmap
*/
private void setUpShader(Bitmap bitmap) {
mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

int dWidth = bitmap.getWidth();
int dHeight = bitmap.getHeight();

int vWidth = width;
int vHeight = height;
if (hasBorder) {
vWidth -= 2 * borderWidth;
vHeight -= 2 * borderWidth;
}
if (dWidth == vWidth && dHeight == vHeight) {

} else {
float scale = 1.0f;
float dx = 0, dy = 0;

if (dWidth * vHeight > vWidth * dHeight) {
scale = (float) vHeight / (float) dHeight;
dx = (vWidth - dWidth * scale) * 0.5f;
} else {
scale = (float) vWidth / (float) dWidth;
dy = (vHeight - dHeight * scale) * 0.5f;
}

mMatrix.setScale(scale, scale);
if (hasBorder) {//有边框的情况,view视图缩小了,所以需要对图片移动一个边框宽度的处理
dx += borderWidth;
dy += borderWidth;
}
//上一个缩放操作完成之后,进行移动(把图片中心与视图中心对应,这样保证图片居中,而原来图片是左上角对应视图左上角),与pre对应

mMatrix.postTranslate(dx, dy);

mBitmapShader.setLocalMatrix(mMatrix);
}

mPaintDrawable.setShader(mBitmapShader);
}


里面的主要逻辑就是用matrix去设置BitmapShader的缩放效果,这里我们参照的是imageView的源码:

private void configureBounds() {
if (mDrawable == null || !mHaveFrame) {
return;
}

final int dwidth = mDrawableWidth;
final int dheight = mDrawableHeight;

final int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
final int vheight = getHeight() - mPaddingTop - mPaddingBottom;

final boolean fits = (dwidth < 0 || vwidth == dwidth)
&& (dheight < 0 || vheight == dheight);

if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
/* If the drawable has no intrinsic size, or we're told to
scaletofit, then we just fill our entire view.
*/
mDrawable.setBounds(0, 0, vwidth, vheight);
mDrawMatrix = null;
} else {
// We need to do the scaling ourself, so have the drawable
// use its native size.
mDrawable.setBounds(0, 0, dwidth, dheight);

if (ScaleType.MATRIX == mScaleType) {
// Use the specified matrix as-is.
//省略...
} else if (fits) {
// The bitmap fits exactly, no transform needed.
mDrawMatrix = null;
} else if (ScaleType.CENTER == mScaleType) {
// Center bitmap in view, no scaling.
//省略...
} else if (ScaleType.CENTER_CROP == mScaleType) {
mDrawMatrix = mMatrix;

float scale;
float dx = 0, dy = 0;

if (dwidth * vheight > vwidth * dheight) {
scale = (float) vheight / (float) dheight;
dx = (vwidth - dwidth * scale) * 0.5f;
} else {
scale = (float) vwidth / (float) dwidth;
dy = (vheight - dheight * scale) * 0.5f;
}

mDrawMatrix.setScale(scale, scale);
mDrawMatrix.postTranslate(Math.round(dx), Math.round(dy));
} else if (ScaleType.CENTER_INSIDE == mScaleType) {

//省略...
} else {
// Generate the required transform.

//省略...scaleTypeToScaleToFit(mScaleType));
}
}
}


矩阵的变换可以参考文章最后的链接,矩阵post,pre,set我在上面的注释也写了,基本上就是set为直接变换,pre和post分别是变换之前和变换之后再进行后续的变换。

至此,bitmap的准备工作就完成了,我们就可以调用canvas的drawCircle来绘制圆形图片了。上面提到了边框的绘制,这里我们就来说一下,其实原理是一样的,无非就是把画笔paint设置为STOKE模式,并且在计算Rect的四个点的坐标的时候边框和内容要注意一下,可以把边框设置为半透明模式,看看图片是否绘制到边框下面了,如果两者边缘正好贴合,则说明半径或者矩形计算的正确,否则就是计算有误。另外一点就是图片的画笔和边框的最好区分开来,这样各司其职,否则就比较混乱。边框的绘制还有一点需要注意的就是,它的半径是圆心到边框宽度的中间,而不是边缘,因为是空心的。

下面就是圆角图片了,这个则是调用canvas的drawRoundRect方法,首先第一个参数就是一个矩形,他是圆角的外接矩形,然后就是圆角的x,y半径了,最后一个参数是画笔。圆角的情况一共可以分为15种,四个角全部是圆的,另外就是单个圆角的组合,这里我才用了枚举类型:

/**
* 圆角的类型
* 顺时针1234四个角,四个角可分为1,2,3,4,12,13,14,23,24,34,123,124,134,234,1234十五种情况
*/
public enum CornerType {
ALL,
TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT,
TOP_LEFT_TOP_RIGHT, TOP_LEFT_BOTTOM_RIGHT, TOP_LEFT_BOTTOM_LEFT,
TOP_RIGHT_BOTTOM_RIGHT, TOP_RIGHT_BOTTOM_LEFT, BOTTOM_RIGHT_BOTTOM_LEFT,
TOP_LEFT_TOP_RIGHT_BOTTOM_RIGHT, TOP_LEFT_TOP_RIGHT_BOTTOM_LEFT, TOP_LEFT_BOTTOM_RIGHT_BOTTOM_LEFT, TOP_RIGHT_BOTTOM_RIGHT_BOTTOM_LEFT
}


而这些参数都可以在xml属性中配置,也可以通过代码去set具体的type。下面是具体绘制代码:

case ALL:
if (hasBorder) {
//Math.ceil进位来保证不留白,扩大一点绘制区域
RectF rf = new RectF(borderWidth, borderWidth, (float) Math.ceil(width - borderWidth), (float) Math.ceil(height - borderWidth));

canvas.drawRoundRect(rf, cr, cr, mPaintDrawable);
} else {
RectF rf = new RectF(0, 0, width, height);
//corner为圆心到边缘的距离
canvas.drawRoundRect(rf, cornerRadius, cornerRadius, mPaintDrawable);
}
break;
case TOP_LEFT:
//分块绘制,也可以采用path来绘制
if (hasBorder) {
//                        path.reset();
//                        path.moveTo(borderWidth,borderWidth+cr+borderWidth);
//                        path.addArc(new RectF(borderWidth, borderWidth, cr * 2f + borderWidth, cr * 2f + borderWidth), 180, 90);
//                        path.lineTo(width-borderWidth,borderWidth);
//                        path.lineTo(width-borderWidth,height-borderWidth);
//                        path.lineTo(borderWidth,height-borderWidth);
//                        path.close();
//                        canvas.drawPath(path,mPaintDrawable);

//                        canvas.drawRoundRect(new RectF(borderWidth, borderWidth, cr * 2f+borderWidth, cr * 2f+borderWidth),cr,cr,mPaintDrawable);
canvas.drawArc(new RectF(borderWidth, borderWidth, cornerRadius * 2f - borderWidth, cr * 2f + borderWidth), 180, 90, true, mPaintDrawable);
canvas.drawRect(new RectF(borderWidth, cornerRadius, cornerRadius, height - borderWidth), mPaintDrawable);
canvas.drawRect(new RectF(cornerRadius, borderWidth, width - borderWidth, height - borderWidth), mPaintDrawable);
} else {
//drawRoundRect也可以,不过多绘制了一部分圆
canvas.drawRoundRect(new RectF(0, 0, cornerRadius * 2f, cornerRadius * 2f), cornerRadius, cornerRadius, mPaintDrawable);
canvas.drawRect(new RectF(0, cornerRadius, cornerRadius, height), mPaintDrawable);
canvas.drawRect(new RectF(cornerRadius, 0, width, height), mPaintDrawable);
}
break;


这里我只列举了两个情况:全部圆角和左上角圆角,可以看到我们绘制的方案可以多种,单个圆角组合的情况其实就是把图片拆分为不同区块,然后连接起来去绘制,原理就是这么简单!既然可以用path去绘制各种各样的图形,那么我们是不是可以绘制一些特殊形状呢,比如五角星,小熊,三角形,五边形,六边形。。。等等,自己可以随意去扩展了,这里我用了一个OtherType来表示特殊形状:

/**
* 其他类型,如五角星,小熊,六边形等等不规则的
*/
public enum OtherType {
STAR, BEAR, HEXAGON
}


具体使用的时候可以自己根据具体情况去扩展。这里我只列出了六边形:

path.reset();
path.moveTo(width * 0.25f, 0);
path.lineTo(width * 0.75f, 0);
path.lineTo(width, height * 0.5f);
path.lineTo(width * 0.75f, height);
path.lineTo(width * 0.25f, height);
path.lineTo(0, height * 0.5f);
path.close();
canvas.drawPath(path, mPaintDrawable);


可以看到用到了一些数学知识,尤其是当你画五角星的时候,这些特殊形状基本上都是数学知识的运用和path的api的基本使用。

最后就是状态的保存与恢复了,防止我们的自定义view出现异常,核心思想就是保存我们设置的属性,然后在恢复的时候去重新绘制就可以恢复销毁之前的原状了。

@Nullable
@Override
protected Parcelable onSaveInstanceState() {
super.onSaveInstanceState();
//状态保存
Bundle bundle = new Bundle();
bundle.putBoolean("hasBorder", hasBorder);
bundle.putBoolean("isCircle", isCircle);
bundle.putBoolean("isBlur", isBlur);
bundle.putBoolean("isOval", isOval);

bundle.putFloat("cornerRadius", cornerRadius);
bundle.putFloat("borderWidth", borderWidth);
bundle.putFloat("blurRadius", blurRadius);

bundle.putInt("borderColor", borderColor);
bundle.putInt("defaultColor", defaultColor);

bundle.putSerializable("otherType", otherType);
bundle.putSerializable("cornerType", cornerType);
return bundle;
}

@Override
protected void onRestoreInstanceState(Parcelable state) {
super.onRestoreInstanceState(state);
//状态恢复
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
setHasBorder(bundle.getBoolean("hasBorder"));
setCircle(bundle.getBoolean("isCircle"));
setBlur(bundle.getBoolean("isBlur"));
setOval(bundle.getBoolean("isOval"));

setCornerRadius(bundle.getFloat("cornerRadius"));
setBorderWidth(bundle.getFloat("borderWidth"));
setBlurRadius(bundle.getFloat("blurRadius"));

setBorderColor(bundle.getInt("borderColor"));
setDefaultColor(bundle.getInt("defaultColor"));

setOtherType((OtherType) bundle.getSerializable("otherType"));
setCornerType((CornerType) bundle.getSerializable("cornerType"));
}
reDraw();
}


最后就是自定义属性了:

<declare-styleable name="baselib_BaseImageView">
<!--其他一切不规则图案-->
<attr name="baselib_other_type">
<enum name="STAR" value="1"/>
<enum name="BEAR" value="2"/>
<enum name="HEXAGON" value="3"/>
</attr>
<!--顺时针1234四个角,四个角可分为1,2,3,4,12,13,14,23,24,34,123,124,134,234,1234十五种情况-->
<attr name="baselib_corner_type">
<enum name="ALL" value="1234"/>
<enum name="TOP_LEFT" value="1"/>
<enum name="TOP_RIGHT" value="2"/>
<enum name="BOTTOM_RIGHT" value="3"/>
<enum name="BOTTOM_LEFT" value="4"/>
<enum name="TOP_LEFT_TOP_RIGHT" value="12"/>
<enum name="TOP_LEFT_BOTTOM_RIGHT" value="13"/>
<enum name="TOP_LEFT_BOTTOM_LEFT" value="14"/>
<enum name="TOP_RIGHT_BOTTOM_RIGHT" value="23"/>
<enum name="TOP_RIGHT_BOTTOM_LEFT" value="24"/>
<enum name="BOTTOM_RIGHT_BOTTOM_LEFT" value="34"/>
<enum name="TOP_LEFT_TOP_RIGHT_BOTTOM_RIGHT" value="123"/>
<enum name="TOP_LEFT_TOP_RIGHT_BOTTOM_LEFT" value="124"/>
<enum name="TOP_LEFT_BOTTOM_RIGHT_BOTTOM_LEFT" value="134"/>
<enum name="TOP_RIGHT_BOTTOM_RIGHT_BOTTOM_LEFT" value="234"/>
</attr>
<!--椭圆-->
<attr name="baselib_is_oval" format="boolean"/>
<!--是否是圆形图片-->
<attr name="baselib_is_circle" format="boolean"/>

<!--=====================上面是互斥的属性,下面的可以与上面的共存=====================-->
<!--是否带边框-->
<attr name="baselib_has_border" format="boolean"/>
<!--边框颜色-->
<attr name="baselib_border_color" format="color|reference"/>
<!--边框宽度-->
<attr name="baselib_border_width" format="dimension"/>
<!--圆角的度数(代表所有角,不再单独针对部分圆角的情况提供某一个角的度数)-->
<attr name="baselib_corner_radius" format="dimension"/>
<!--是否高斯模糊-->
<attr name="baselib_is_blur" format="boolean"/>
<!--高斯模糊半径-->
<attr name="baselib_blur_radius" format="float"/>
<!--当图片为空的时候,默认颜色-->
<attr name="baselib_default_color" format="color|reference"/>
</declare-styleable>


所有属性都可以通过代码set和get,而且可以自己扩展,相信这个imageview基本上满足日常开发了,最后如果使用glide加载图片的话,在getBitmap方法里面需要加上一种情况:

else if (drawable instanceof GlideBitmapDrawable) {
bitmap = ((GlideBitmapDrawable) drawable).getBitmap();
}


到此整个imageview就讲解完了,老规矩,有疑问的同学可以在下方留言,最后放出源码:

源码下载

参考:

https://www.jianshu.com/p/94093d8b168a

http://blog.csdn.net/programchangesworld/article/details/49078387
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: