专栏名称: 鸿洋
你好,欢迎关注鸿洋的公众号,每天为您推送高质量文章,让你每天都能涨知识。点击历史消息,查看所有已推送的文章,喜欢可以置顶本公众号。此外,本公众号支持投稿,如果你有原创的文章,希望通过本公众号发布,欢迎投稿。
目录
相关文章推荐
鸿洋  ·  Android | ... ·  昨天  
stormzhang  ·  被裁员就是员工的错了? ·  2 天前  
stormzhang  ·  特斯拉画的饼,圆上了? ·  4 天前  
鸿洋  ·  裁员在家如何保持高效学习 ·  1 周前  
51好读  ›  专栏  ›  鸿洋

Android | 多种方式实现图片圆角矩形和圆形效果

鸿洋  · 公众号  · android  · 2024-10-18 08:35

正文

本文作者:mqcoder,原文发布于:代码说

在项目开发中,为了让图片显示得更加美观,通常 UI 会设计成圆角矩形或圆形效果。本文将介绍几种常见的实现方式,并提供对应的代码示例。

1
方式一:ViewOutlineProvider可以设置圆角矩形、椭圆、圆形等

ViewOutlineProvider 是 Android 5.0 引入的一个类,用来定义视图的轮廓(outline)。可以通过它来实现圆角矩形、椭圆、圆形等效果。
//设置成扩展方法
fun View.clipToRoundView(type: Int = RoundImgView.SHAPE_ROUND_RECT) {
    if (Build.VERSION.SDK_INT >= 21) {
        outlineProvider = object : ViewOutlineProvider() {
            override fun getOutline(view: View?, outline: Outline?) {
                if (view == nullreturn
                if (type == RoundImgView.SHAPE_ROUND_RECT) {
                    //设置一个矩形的轮廓,并指定其圆角半径
                    outline?.setRoundRect(00, view.width, view.height, 15.dp2px().toFloat())
                } else {
                    //设置成椭圆或者圆形
                    outline?.setOval(00, view.width, view.height)
                }
            }
        }
        //视图会根据outlineProvider提供的轮廓进行裁剪。任何超出轮廓的部分都会被裁剪掉
        clipToOutline = true
    }
}


可以看到通过outline中的setRoundRect/setOval方法来设置圆角矩形、椭圆等形状,还有其他API方法可以自行了解。在Activity中使用:
private val mIvTarget: AppCompatImageView by id(R.id.iv_round_img)
private val mIvTarget2: AppCompatImageView by id(R.id.iv_round_img2)

val bitmap = BitmapFactory.decodeResource(resources, R.drawable.icon_cat_w)
mIvTarget.clipToRoundView(RoundImgView.SHAPE_ROUND_RECT)
mIvTarget.setImageBitmap(bitmap)

mIvTarget2.clipToRoundView(RoundImgView.SHAPE_CIRCLE)
mIvTarget2.setImageBitmap(bitmap)


效果如下:

效果图

2
方式二:使用 Glide 进行图片加载和圆角处理

Glide 是一个强大的图片加载库,通过它的 RequestOptions 可以轻松实现图片的圆角处理。
//1、图片设置的不是CenterCrop
Glide.with(this)
     .load(R.drawable.icon_cat_w)
     .transform(RoundedCorners(16.dp2px()))
     .into(mIvTarget)

//2、如果图片设置的是CenterCrop,可能会导致圆角效果被 CenterCrop 操作覆盖,最终看不到圆角效果,需要用下面的方式处理CenterCrop与圆角矩形冲突问题
val requestOptions = RequestOptions().transform(CenterCrop(), RoundedCorners(16.dp2px()))
Glide.with(this)
     .load(R.drawable.icon_cat_w)
     .apply(requestOptions)
     .into(mIvTarget)

3
方式三:Canvas.clipPath()


自定义 ImageView 并重写 onDraw() 方法,通过 Canvas.clipPath() 实现图片的圆角矩形和圆形效果。
/**
 * 通过clipPath的方式实现圆角矩形和圆形图片
 *
 * @param context
 * @param attrs
 * @param defStyleAttr
 */

class RoundImgView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {

    companion object {
        const val SHAPE_ROUND_RECT = 0
        const val SHAPE_CIRCLE = 1
    }

    private val path = Path()
    private val strokePath = Path()
    private var cornerRadius = 10.dp2px().toFloat()
    private var mStrokeWidth = 10.dp2px().toFloat()
    private var mShapeType = SHAPE_ROUND_RECT
    private var isHasStroke = false //是否设置描边

    // 创建一个画笔
    val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.BLUE
        style = Paint.Style.STROKE
        strokeWidth = mStrokeWidth
    }

    override fun onDraw(canvas: Canvas) {
        //canvas.drawColor(Color.RED)
        //设置描边
        if (isHasStroke) {
            processPath(strokePath) //设置path
            canvas.save()
            canvas.drawPath(strokePath, paint)
            canvas.restore()
        }
        processPath(path)
        canvas.clipPath(path)
        super.onDraw(canvas)
    }

    private fun processPath(path: Path) {
        if (mShapeType == SHAPE_CIRCLE) {
            //圆形
            val radius = (maxOf(width, height) - mStrokeWidth) / 2f
            path.addCircle(width / 2f, height / 2f, radius, Path.Direction.CW)
        } else {
            //圆角矩形
            path.addRoundRect(
                mStrokeWidth, mStrokeWidth,
                width.toFloat() - mStrokeWidth,
                height.toFloat() - mStrokeWidth,
                floatArrayOf(
                    cornerRadius, cornerRadius, cornerRadius, cornerRadius,
                    cornerRadius, cornerRadius, cornerRadius, cornerRadius
                ),
                Path.Direction.CW
            )
        }
    }

    /**
     * 设置描边宽度
     * @param width 宽度
     */

    fun setStrokeWidth(width: Float): RoundImgView {
        this.mStrokeWidth = width
        paint.strokeWidth = mStrokeWidth
        isHasStroke = true
        return this
    }

    /**
     * 设置圆角半径
     * @param radius 半径
     */

    fun setCornerRadius(radius: Float): RoundImgView {
        cornerRadius = radius
        return this
    }

    /**
     * 设置图片类型
     * @param type
     */

    fun setShapeType(type: Int): RoundImgView {
        this.mShapeType = type
        return this
    }

    override fun setImageBitmap(bm: Bitmap?) {
        super.setImageBitmap(bm)
    }
}


Activity中使用:
private val mIvCustomImg: RoundImgView by id(R.id.iv_custom_img)
private val mIvCustomImg2: RoundImgView by id(R.id.iv_custom_img2)

val bitmap = BitmapFactory.decodeResource(resources, R.drawable.icon_cat_w)
mIvCustomImg.setCornerRadius(15.dp2px().toFloat())
            .setShapeType(RoundImgView.SHAPE_ROUND_RECT)
            .setStrokeWidth(10f)
            .setImageBitmap(bitmap)

mIvCustomImg2.setCornerRadius(15.dp2px().toFloat())
            .setShapeType(RoundImgView.SHAPE_CIRCLE)
            .setStrokeWidth(10f)
            .setImageBitmap(bitmap)


效果图:

效果图

4
方式四:CardView

通过 CardView 的 app:cardCornerRadius 属性,可以非常方便地实现圆角效果。

<androidx.cardview.widget.CardView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        app:cardCornerRadius="10dp">


   <androidx.appcompat.widget.AppCompatImageView
            android:layout_width="150dp"
            android:layout_height="150dp"
            android:scaleType="centerCrop"
            android:src="@drawable/icon_cat_w" />

androidx.cardview.widget.CardView>

5
方式五:BitmapShader

BitmapShader 是 Android 中的一种着色器,通过它可以实现自定义的圆角和圆形图片。
/**
 * @param bitmap  bitmap
 * @param outWidth 输出的宽
 * @param outHeight 输出的高
 * @param radius 半径
 * @param border 描边
 * @param shapeType 图形类型:圆角矩形 or 圆形
 */

private fun getBitmapByShader(
    bitmap: Bitmap?,
    outWidth: Int,
    outHeight: Int,
    radius: Int,
    border: Int,
    shapeType: Int = RoundImgView.SHAPE_ROUND_RECT
)
: Bitmap? {
    if (bitmap == null || bitmap.height <= 0 || bitmap.width <= 0) {
        return null
    }
    //缩放比例
    val scale = minOf(outWidth.toFloat() / bitmap.width, outHeight.toFloat() / bitmap.height)
    //创建矩阵
    val matrix = Matrix().apply {
        setScale(scale, scale)
    }
    //创建shader
    val shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP).apply {
        setLocalMatrix(matrix)
    }
    //通过shader着色器来绘制图像
    val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        this.shader = shader
    }

    return Bitmap.createBitmap(outWidth, outHeight, Bitmap.Config.ARGB_8888).also { output ->
        Canvas(output).apply {
            val rect = RectF(
                border.toFloat(),
                border.toFloat(),
                (outWidth - border).toFloat(),
                (outHeight - border).toFloat()
            )
            if (shapeType == RoundImgView.SHAPE_ROUND_RECT) {
                //绘制圆角矩形
                drawRoundRect(rect, radius.toFloat(), radius.toFloat(), paint)
            } else {
                //绘制圆形
                drawCircle(outWidth / 2f, outHeight / 2f, outWidth / 2f, paint)
            }

            if (border > 0) {
                //如果有描边,绘制描边
                val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
                    color = Color.RED
                    style = Paint.Style.STROKE
                    strokeWidth = border.toFloat()
                }
                if (shapeType == RoundImgView.SHAPE_ROUND_RECT) {
                    //绘制圆角矩形
                    drawRoundRect(rect, radius.toFloat(), radius.toFloat(), strokePaint)
                } else {
                    //绘制圆形
                    drawCircle(outWidth / 2f, outHeight / 2f, (outWidth - border) / 2f, strokePaint)
                }
            }
        }
    }
}


Activity中使用:
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.icon_cat_w)
val targetBitmap = getBitmapByShader(bitmap, 200.dp2px(), 200.dp2px(), 20.dp2px(), 5.dp2px(), RoundImgView.SHAPE_ROUND_RECT)
mIvTarget.setImageBitmap(targetBitmap)


或者在自定义Drawable中使用BitmapShader:
class RoundDrawable(val bitmap: Bitmap, val targetWH: Float) : Drawable() {

    private val paint = Paint().apply {
        //缩放比例
        val scale = minOf(targetWH / bitmap.width, targetWH / bitmap.height)
        //创建矩阵
        val matrix = Matrix().apply {
            setScale(scale, scale)
        }
        isAntiAlias = true
        shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP).apply {
            setLocalMatrix(matrix)
        }
    }

    override fun draw(canvas: Canvas) {
        val rectF = RectF(0f, 0f, targetWH, targetWH)
        canvas.drawRoundRect(rectF, 20f, 20f, paint)
    }

    override fun setAlpha(alpha: Int) {
        paint.alpha = alpha
    }

    override fun setColorFilter(colorFilter: ColorFilter?) {
        paint.colorFilter = colorFilter
    }

    override fun getOpacity()Int = PixelFormat.TRANSLUCENT
}


Activity中:

val roundDrawable = RoundDrawable(bitmap, 200.dp2px().toFloat())
mIvTarget.setImageDrawable(roundDrawable)

6
方式六:RoundedBitmapDrawable

RoundedBitmapDrawable 是 Android 提供的一个工具类,用于处理圆形或圆角矩形的图片显示。

val bitmap = BitmapFactory.decodeResource(resources, R.drawable.icon_cat_w)
val roundBitmapDrawable = RoundedBitmapDrawableFactory.create(resources, bitmap).apply {
      paint.isAntiAlias = true
      cornerRadius = 20.dp2px().toFloat()
}
mIvTarget.setImageDrawable(roundBitmapDrawable)

7
结论


以上介绍了几种常见的在 Android 中实现图片圆角矩形和圆形效果的方法,每种方式都有其使用场景和特点。推荐优先使用系统已经实现好的,比如ViewOutlineProvider 或者 优秀的Glide 图片加载库,如果有额外样式,可以按需实现。


最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!


推荐阅读

探索JNI:基础、最佳实践、性能优化与安全策略
Perfetto 快速上手指南
Android 复杂项目崩溃率收敛至0.01%实践



扫一扫 关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~


┏(^0^)┛明天见!