封装ShapeableImageView,简便使用圆形、圆角、单圆角、描边、支持gif等。

附带源码。

加载图片一直使用Glide + ImageView,在正式环境发现加载gif图片时,设定的圆角没有生效,解决方案就是glide加载需要使用asGif转化,这样很不友好,每个地方都要判断gif进行转化,所以想到ShapeableImageView替代ImageView。

ShapeableImageView是个强大的图片控件,支持圆形/圆角/单圆角/描边设置,使用它的时候,需要设置不同的style,比如:

复制代码
<style name="SIVCircleStyle">
    <!--圆形-->
    <item name="cornerFamily">rounded</item>
    <item name="cornerSize">50%</item>
</style>

<style name="SIVCorner12Style">
    <!--12dp圆角-->
    <item name="cornerFamily">rounded</item>
    <item name="cornerSize">12dp</item>
</style>

<style name="SIVCorner15Style">
    <!--15dp圆角-->
    <item name="cornerFamily">rounded</item>
    <item name="cornerSize">15dp</item>
</style>

使用方法:app:shapeAppearance="@style/SIVCircleStyle"

这样使用很不灵活,比如圆角大小发生变化,需要重新写个style。

所以需要自定义ShapeableImageView,自定义属性来设置圆形/圆角/单圆角/描边,代码如下:

复制代码
/**
 * 项目公共ImageView,适用于加载圆形/圆角、带有边框的场景
 * 默认属性
 * scaleType:CenterCrop()
 * 设置描边strokeWidth,自动设置padding防止描边被截断。
 * 没设置描边的strokeColor默认为null
 *
 * 具有以下优势:
 * 1、支持圆形、统一圆角、4个角自定义圆角
 * 2、支持边框
 * 3、支持圆角gif
 *
 * 使用说明:
 * 1、可以通过xml设置圆形/圆角数值--建议
 * 2、也可以通过代码设置圆形/圆角数值
 * 3、设置strokeColor + strokeWidth,自动设置padding防止描边被截断
 */
class AppImageView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ShapeableImageView(context, attrs, defStyleAttr) {

    init {
        if (strokeWidth > 0) {
            // 自动设置padding,padding大小是strokeWidth一半,防止描边被截断。
            setPadding((strokeWidth / 2).roundToInt())
        } else {
            // 设置默认无描边
            strokeColor = null
        }
        // 设置默认缩放类型为中心裁剪
        scaleType = ScaleType.CENTER_CROP

        context.withStyledAttributes(attrs, R.styleable.AppImageView) {
            val cornerRadius = getDimension(R.styleable.AppImageView_image_cornerRadius, -1f)
            val topLeftRadius = getDimension(R.styleable.AppImageView_image_topLeftRadius, -1f)
            val topRightRadius = getDimension(R.styleable.AppImageView_image_topRightRadius, -1f)
            val bottomLeftRadius = getDimension(R.styleable.AppImageView_image_bottomLeftRadius, -1f)
            val bottomRightRadius = getDimension(R.styleable.AppImageView_image_bottomRightRadius, -1f)
            val isCircle = getBoolean(R.styleable.AppImageView_image_circle, false)
            if (isCircle) {
                // 圆形模式 - 忽略所有圆角设置
                setCircle()
            } else if (cornerRadius > 0) {
                // 统一圆角模式
                setRoundRadius(cornerRadius)
            } else {
                // 单独圆角模式
                setRoundRadii(topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius)
            }
        }
    }

    /**
     * 设置圆形
     */
    fun setCircle() {
        shapeAppearanceModel = ShapeAppearanceModel().toBuilder()
            .setAllCornerSizes(RelativeCornerSize(0.5f))
            .build()
    }

    /**
     * 设置圆角,适用于4个圆角一样大小
     * @param radius 圆角大小
     */
    fun setRoundRadius(radius: Float) {
        if (radius <= 0) return
        shapeAppearanceModel = ShapeAppearanceModel().toBuilder()
            .setAllCornerSizes(radius)
            .build()
    }

    /**
     * 设置圆角,适用于4个圆角大小分开设置
     * @param topLeftRadius 左上 圆角大小
     * @param topRightRadius 右上 圆角大小
     * @param bottomLeftRadius 左下 圆角大小
     * @param bottomRightRadius 右下 圆角大小
     */
    fun setRoundRadii(topLeftRadius: Float = 0f, topRightRadius: Float = 0f, bottomLeftRadius: Float = 0f, bottomRightRadius: Float = 0f) {
        if (topLeftRadius <= 0f &&
            topRightRadius <= 0f &&
            bottomLeftRadius <= 0f &&
            bottomRightRadius <= 0f) return
        val builder = ShapeAppearanceModel().toBuilder()
        if (topLeftRadius > 0) {
            builder.setTopLeftCornerSize(topLeftRadius)
        }
        if (topRightRadius > 0) {
            builder.setTopRightCornerSize(topRightRadius)
        }
        if (bottomLeftRadius > 0) {
            builder.setBottomLeftCornerSize(bottomLeftRadius)
        }
        if (bottomRightRadius > 0) {
            builder.setBottomRightCornerSize(bottomRightRadius)
        }
        shapeAppearanceModel = builder.build()
    }
}

自定义属性放在attrs.xml中,代码如下:

复制代码
<declare-styleable name="AppImageView">
    <attr name="image_cornerRadius" format="dimension" />
    <attr name="image_topLeftRadius" format="dimension" />
    <attr name="image_topRightRadius" format="dimension" />
    <attr name="image_bottomLeftRadius" format="dimension" />
    <attr name="image_bottomRightRadius" format="dimension" />
    <attr name="image_circle" format="boolean" />
</declare-styleable>

使用方式:

复制代码
圆形:
<xxx.AppImageView
    android:id="@+id/iv_avatar"
    android:layout_width="144dp"
    android:layout_height="144dp"
    android:layout_marginStart="20dp"
    android:layout_marginTop="20dp"
    android:src="@drawable/ic_mine_un_login"
    app:image_circle="true"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

四个圆角大小相同:
<xxx.AppImageView
    android:id="@+id/iv_avatar"
    android:layout_width="144dp"
    android:layout_height="144dp"
    android:layout_marginStart="20dp"
    android:layout_marginTop="20dp"
    android:src="@drawable/ic_mine_un_login"
    app:image_cornerRadius="20dp"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

分别设置四个圆角大小:
<xxx.AppImageView
    android:id="@+id/iv_avatar"
    android:layout_width="144dp"
    android:layout_height="144dp"
    android:layout_marginStart="20dp"
    android:layout_marginTop="20dp"
    android:src="@drawable/ic_mine_un_login"
    app:image_bottomLeftRadius="10dp"
    app:image_bottomRightRadius="20dp"
    app:image_topLeftRadius="20dp"
    app:image_topRightRadius="10dp"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

解释下为什么要设置strokeColor=null,因为在低版本手机不设置strokeColor有警告log。

再说下描边问题,借用github图,需要设置paddingstrokeWidth的一半,可以参考代码注释,roundToInt()防止设置太小导致padding为0。

再更新一点,这里只是做个记录,以下代码不用copy。

ShapeAppearanceModel类中的MaterialShapeUtils.createDefaultCornerTreatment()创建的是RoundedCornerTreatment,所以默认就是圆角类型,不需要如下设置:

复制代码
fun setRoundRadius(radius: Float) {
    if (radius <= 0) return
    shapeAppearanceModel = ShapeAppearanceModel().toBuilder()
        .setAllCorners(CornerFamily.ROUNDED, radius)
        .build()
}