Kotlin自定义ImageView圆角实现求助:Drawable无效,不会用View.setToOutline
首先得说清楚你当前方案失效的原因:
你用
android:background="@drawable/rounded_corner"设置的是ImageView的背景层,但ImageView实际显示的图片是通过src/app:srcCompat设置的,这个图片会绘制在背景之上,再加上你用了fitXY的scaleType让图片填满整个View,背景的圆角就被完全盖住了,自然看不到效果。
下面给你三种可行的解决方案,按推荐程度排序:
方案1:用Material Components的ShapeableImageView(最省心)
Google的Material组件已经封装了圆角、裁剪、形状变换等功能,你只需要修改自定义View的继承类,就能保留你的比例逻辑同时实现圆角:
修改你的DynamicHeightImageView代码:
import android.content.Context import android.util.AttributeSet import com.google.android.material.imageview.ShapeableImageView class DynamicHeightImageView : ShapeableImageView { private var whRatio = 0f constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet) : super(context, attrs) fun setRatio(ratio: Float) { whRatio = ratio requestLayout() // 比例变化时触发重新测量 } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) if (whRatio != 0f) { val width = measuredWidth val height = (whRatio * width).toInt() setMeasuredDimension(width, height) } } }
然后在布局里配置圆角样式:
<com.georgcantor.wallpaperapp.util.DynamicHeightImageView android:id="@+id/pictureImageView" android:layout_width="match_parent" android:layout_margin="5dp" android:layout_height="match_parent" android:contentDescription="@string/image_item" android:foreground="?android:attr/selectableItemBackground" android:scaleType="fitXY" app:shapeAppearanceOverlay="@style/RoundedImageViewStyle" />
在styles.xml里定义圆角样式:
<style name="RoundedImageViewStyle"> <item name="cornerFamily">rounded</item> <!-- 圆角类型,可选rounded/roundedCut --> <item name="cornerSize">10dp</item> <!-- 圆角大小 --> </style>
这样设置后,图片会自动被裁剪成圆角,同时保留你自定义的比例逻辑,非常省心。
方案2:用原生OutlineProvider(对应View.setToOutline)
如果你不想引入Material组件,可以用Android原生的OutlineProvider来实现裁剪,这也是View.setToOutline相关的核心用法:
修改你的DynamicHeightImageView,添加圆角支持:
import android.content.Context import android.util.AttributeSet import android.view.View import android.view.ViewOutlineProvider import androidx.appcompat.widget.AppCompatImageView import kotlin.math.min class DynamicHeightImageView : AppCompatImageView { private var whRatio = 0f private var cornerRadius = 0f // 圆角大小(像素单位) constructor(context: Context) : super(context) { initView() } constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initView() // 可选:从XML属性读取圆角大小(复用系统的radius属性) val ta = context.obtainStyledAttributes(attrs, intArrayOf(android.R.attr.radius)) cornerRadius = ta.getDimension(0, 0f) ta.recycle() } private fun initView() { clipToOutline = true // 必须开启这个,Outline裁剪才会生效 // 设置OutlineProvider,用来定义View的裁剪形状 outlineProvider = object : ViewOutlineProvider() { override fun getOutline(view: View, outline: android.graphics.Outline) { // 计算圆角:取View宽高中较小值的一半,避免圆角变形 val finalRadius = min(cornerRadius, min(view.width, view.height) / 2f) // 设置圆角矩形的Outline outline.setRoundRect(0, 0, view.width, view.height, finalRadius) } } } fun setRatio(ratio: Float) { whRatio = ratio requestLayout() } // 对外暴露方法,用dp设置圆角 fun setCornerRadiusDp(dp: Float) { cornerRadius = dp * resources.displayMetrics.density invalidateOutline() // 刷新Outline,让圆角生效 } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) if (whRatio != 0f) { val width = measuredWidth val height = (whRatio * width).toInt() setMeasuredDimension(width, height) } } }
然后你可以在代码里设置圆角:
// 在Activity/Fragment里 pictureImageView.setCornerRadiusDp(10f)
或者直接在XML里设置:
<com.georgcantor.wallpaperapp.util.DynamicHeightImageView ... android:radius="10dp" />
关于View.setToOutline的说明
View.setToOutline是API 30新增的方法,它可以直接给View设置一个固定的Outline对象,比如:
val outline = Outline() outline.setRoundRect(0, 0, view.width, view.height, 10f * resources.displayMetrics.density) view.setToOutline(outline) view.clipToOutline = true
但这种方式的问题是:如果View的大小发生变化(比如你的DynamicHeightImageView会根据比例调整高度),这个Outline不会自动更新。所以更推荐用OutlineProvider,它会在View大小变化时自动重新计算Outline,适配性更好。
方案3:自定义Drawable实现圆角(不推荐,代码繁琐)
你也可以自定义一个Drawable,在绘制时给图片加上圆角,然后设置为ImageView的src。但这种方式需要自己处理图片缩放、裁剪逻辑,代码量比较大,不如前两种方案高效,这里就不展开了。
最后提醒几个注意点:
- 所有裁剪方案都需要确保
clipToOutline为true(ShapeableImageView已经自动处理) - 如果用OutlineProvider,当View大小变化时,记得调用
invalidateOutline()刷新裁剪形状
内容的提问来源于stack exchange,提问作者AorkGames 360

