您的当前位置:首页正文

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

2024-10-31 来源:个人技术集锦


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

方式一: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 == null) return
                if (type == RoundImgView.SHAPE_ROUND_RECT) {
                    //设置一个矩形的轮廓,并指定其圆角半径
                    outline?.setRoundRect(0, 0, view.width, view.height, 15.dp2px().toFloat())
                } else {
                    //设置成椭圆或者圆形
                    outline?.setOval(0, 0, 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)

效果如下:

方式二:使用 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)

方式三: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)

效果图:

方式四: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>

方式五: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)

方式六: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)

结论

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

Top