简介
实现圆角图片的方法有以下几种,其中的最第三种是参考的Android圆角图片和圆形图片实现总结
- 使用Google官方提供的控件CardView卡片式布局,这个控件提供了圆角半径设置和阴影效果。不过使用需要注意系统版本。
- 使用Glide图片加载框架,利用其提供的RoundedCorners可以设置图片的圆角半径,代码大致如下:
val option = RequestOptions()
.error(R.mipmap.ic_launcher_round)
.transform(RoundedCorners(300))//设置圆角半径
Glide.with(userInfoAvatar)
.applyDefaultRequestOptions(option)
.load(imgUrl)
.into(img)
- 图层覆盖、以及重新绘制(BitmapShader、Xfermode、RoundedBitmapDrawable)具体细节见Android圆角图片和圆形图片实现总结文章写的很好,很详细。
- OutLine
- material包下的ShapeableImageView,这个简直不要太好用哦。可以实现圆角图片甚至是异形图片,当然也有边框绘制。
第四第五两个方法是评论区大佬给补充的知识,谢谢大佬!
正文
强烈建议使用ShapeImageView,不仅可以实现四个角的控制还能控制四条边,就可以画出任何想要的异形。具体怎么实现可以看一下我的这篇文章Android 实现布局凹陷(MaterialShapeDrawable)
OutLine
ShapeableImageView
只需要给这个View设置shapeAppearance属性就可以实现对角形状的控制,以及边框,先看一下效果:
左边是圆角,右边是“切角”
<!-- layout.xml 就是在布局中定义-->
<com.google.android.material.imageview.ShapeableImageView
android:layout_width="200dp"
android:layout_height="wrap_content"
android:scaleType="fitXY"
android:strokeWidth="10dp"
android:strokeColor="@color/purple"
android:src="@drawable/ic_launcher_background"
app:shapeAppearance="@style/RoundAndCutImageStyle" />
<!-- style.xml 可以单独给每个角设置属性-->
<style name="RoundAndCutImageStyle">
<item name="cornerFamilyTopLeft">rounded</item>
<item name="cornerFamilyBottomLeft">rounded</item>
<item name="cornerFamilyTopRight">cut</item>
<item name="cornerFamilyBottomRight">cut</item>
<item name="cornerSize">50%</item>
<item name="cornerSizeBottomLeft">20dp</item>
<item name="cornerSizeTopLeft">20dp</item>
</style>
这里需要解释以下几个属性:
- app:sharedAppearance 给view设置style
- app:sharedAppearanceOverlay 覆盖view的style;用途:在系统主题下可以设置通用的属性,这个时候我们需要自己定义一个特殊的属性,就可以通过app:sharedAppearanceOverlay来覆盖,通用属性分别有三个对应大中小控件的形状控制,三个属性如下:
<!-- 系统主题,使用这个主题的activity包含有material design的控件就会被以下属性修饰形状 -->
<style name="MyAppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="shapeAppearanceLargeComponent">@style/ShapeAppearance.MyApp.LargeComponent</item>
<item name="shapeAppearanceMediumComponent">@style/ShapeAppearance.MyApp.MediumComponent</item>
<item name="shapeAppearanceSmallComponent">@style/ShapeAppearance.MyApp.SmallComponent</item>
</style>
<style name="ShapeAppearance.MyApp.MediumComponent" parent="ShapeAppearance.MaterialComponents.MediumComponent">
<item name="cornerFamily">cut</item>
<item name="cornerSize">8dp</item>
</style>
- cornerFamily 这个属性有两个值
- rounded 圆角
- cut 切角
- cornerSize 控制角的大小,可以为以下这些值
- 百分比 这个不用解释吧,就是长宽的一半,改变view大小会使得半径随之改变
- dimension 就是dp、px、sp等等属性;注意在view大小可能发生改变时尽量不要使用百分比,这样会让你的view形状跟着改变 这两个属性可以单独给每个角设置,cornerFamilyTopLeft、cornerSizeTopLet这样的。
- strokeWidth 边框的宽度
- strokeColor 边框颜色
通过BitmapShader重新绘制
我这里我这里同样是使用的BitmapShader来进行重新绘制,大致思路是和那篇文章写得一样,但是使用了不同的绘制方法,并且也实现了每个角单独绘制不过相对上面的那篇文章会简单一点。开始之前建议先阅读这篇文章哦Android圆角图片和圆形图片实现总结👍
需要用到的各类方法
-
TileMode Shader.TileMode 官网的中文翻译
-
BitmapShader 构造方法
public BitmapShader(Bitmap bitmap,TileMode tileX, TileMode tileY)
paint设置shader之后,当发生绘制时,就会把shader中的bitmap的内容绘制到指定的位置,大致这么理解,有错误欢迎大佬指出,谢谢!
- drawable转bitmap 使用BitmapShader当然要先把drawable转成bitmap
private fun drawableToBitmap(drawable: Drawable): Bitmap {
val bitmap = Bitmap.createBitmap(getTrulyWidth(), getTrulyHeight(), Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)//将画布目标设置为bitmap,这样画布的内容就会直接画到bitmap上
//这里传入的宽高就是减去padding的值
drawable.setBounds(0, 0, getTrulyWidth(), getTrulyHeight())
drawable.draw(canvas)
return bitmap
}
//其实在kotlin中存在一个扩展函数,就是使用kotlin的话直接drawable.toBitmap(...)
fun Drawable.toBitmap(
@Px width: Int = intrinsicWidth,
@Px height: Int = intrinsicHeight,
config: Config? = null): Bitmap
- Canvas.drawDoubleRoundRect,我就是通过这个方法实现了四个角的不同半径绘制以及边框绘制。
- 方法入口,详细注释都在里面
这个方法的灵活性很高,因为内外边框都可控。/** * 这个方法通过内外边框来完成绘制,且内外边框的四个角的圆角半径都可控 * @param outer 外边框的范围 * @param inner 内边框的范围 * @param innerRadii、outerRadii * 这两个float数组分别保存了内外边框的四个角的圆角半径 * 每个数组需要传入8个float,每两个为一组 rx,ry即圆角半径在x,y方向的值 * @param paint 这里我传入的paint里面保存着bitmapShader * 此外需要注意设置Style: * FILL将会绘制到两个矩形(RectF)中间的内容 * STROKE,就是会把两个边框给绘制出来。 */ public void drawDoubleRoundRect(@NonNull RectF outer, @NonNull float[] outerRadii, @NonNull RectF inner, @NonNull float[] innerRadii, @NonNull Paint paint)
注意 内边框不能在外边框的外面,边界超过外边框会导致不绘制。边界可以相等
实践
内容绘制
这里为了简便没有给画笔设置shader,就是单纯的黑色。
-
给外边框设置圆角,内边框不设置
-
外圆内方
-
外圆内圆
-
外边框三圆角,内边框很小
(这个能看见我的内边框吗,内边框位于view的正中间,宽高都为0,看不见吧,这样就已经实现了对四个圆角的控制。)
-
在4的基础上添加了shader,可以看到当内容超过原始边界时,不断的赋值边缘的颜色,说明使用了TileMode.CLAMP
这里给出最后一个的绘制代码
canvas.drawDoubleRoundRect(
outRect,
floatArrayOf(
topLeftRadius,
topLeftRadius,//左上角
topRightRadius,
topRightRadius,//右上角
bottomRightRadius,
bottomRightRadius,//右下角
0f,
0f//左下角
),
//内边框,刚好是位于中间的0X0的矩形,其实只需要保证内边框大小为0就行了
RectF(outWidth/2f,outHeight/2f,outWidth/2f,outHeight/2f),
//内边框四角半径
floatArrayOf(
0f,//rx
0f,//ry
0f,
0f,
0f,
0f,
0f,
0f
),
bitmapPaint//style = Paint.Style.FILL 绘制两个矩形之间的内容;
// bitmap.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
)
拓展
探索
已知画笔设置Paint.Style.FILL时,将会绘制两个矩形之间的内容。那么两个边框有部分内容不重合时会发生什么呢?(虽然内边框必须小于等于外边框,但是当外边框设置了圆角半径时,就可能出现二者存在部分内容不能重合。)
先看一下绘制的边框:
Paint.Style.STROKE
Paint.Style.FILL
结论
Canvas.drawDoubleRoundRect 这个方法绘制两个边框的非交集部分的内容,正好对应PorterDuff.Mode.Xor

详细内容可以查看PorterDuff.Mode
结语
内容难免会有错误,非常欢迎大家指出,对于大家的建议和错误指正,我会及时修改。谢谢!