Android 圆角图片RoundImageView(Canvas.drawDoubleRoundRect)

8,663 阅读5分钟

简介

实现圆角图片的方法有以下几种,其中的最第三种是参考的Android圆角图片和圆形图片实现总结

  1. 使用Google官方提供的控件CardView卡片式布局,这个控件提供了圆角半径设置和阴影效果。不过使用需要注意系统版本。
  2. 使用Glide图片加载框架,利用其提供的RoundedCorners可以设置图片的圆角半径,代码大致如下:
	val option = RequestOptions()
    		.error(R.mipmap.ic_launcher_round)
    		.transform(RoundedCorners(300))//设置圆角半径
   
        Glide.with(userInfoAvatar)
            	.applyDefaultRequestOptions(option)
            	.load(imgUrl)
            	.into(img)
  1. 图层覆盖、以及重新绘制(BitmapShader、Xfermode、RoundedBitmapDrawable)具体细节见Android圆角图片和圆形图片实现总结文章写的很好,很详细。
  2. OutLine
  3. 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圆角图片和圆形图片实现总结👍

需要用到的各类方法

        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,就是单纯的黑色。

  1. 给外边框设置圆角,内边框不设置

  2. 外圆内方

  3. 外圆内圆

  4. 外边框三圆角,内边框很小 (这个能看见我的内边框吗,内边框位于view的正中间,宽高都为0,看不见吧,这样就已经实现了对四个圆角的控制。)

  5. 在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

结语

内容难免会有错误,非常欢迎大家指出,对于大家的建议和错误指正,我会及时修改。谢谢!