带有动态文本的自定义图像,顶部带有背景

Custom image with dynamic text on top with background

我正在尝试在 Kotlin 中创建一个自定义的 ImageViewDrawable,这样可以在运行时在基本图像上绘制动态文件扩展名。最终结果将如下所示。尝试创建自定义 AppCompatImageView class 并覆盖 onDraw() 但没有成功。作为这方面的新手,你能建议我一个好的起点来实现这一目标吗?

编辑

文件扩展名是需要在底图上绘制的文字,背景如附件所示。

您可以在运行时创建一个 LayerDrawable 来叠加两个可绘制对象(一个用于背景,一个用于扩展)并将扩展可绘制对象放置在右下角。

看起来像这样

val layerDrawable = LayerDrawable(
                        arrayOf(
                            AppCompatResources.getDrawable(context, R.drawable.ic_base_sound_file),
                            AppCompatResources.getDrawable(context, R.drawable.ic_aiff_extension)
                        )
                    ).apply {
                        setLayerInset(1, 20, 40, 0, 10)
                    }
imageView.setImageDrawable(layerDrawable)

方法 setLayerInset(index, left, top, right, bottom) 将在位置 'index' 处添加插图到可绘制对象(此处为 1 -> 扩展可绘制对象)。

如果基础映像需要,您也可以使用远程映像。

我可以想到 2 个解决方案。我这里简单分享ideas/approaches,相关代码很容易找到。

  1. 更简单的方法是在您的 xml 中设计此布局。然后创建您的自定义 class 扩展 ViewGroup 并在其构造函数中您可以膨胀视图 xml 并初始化事物。然后你可以定义任何辅助方法,比如 setData(),你可以在其中传递文件扩展名信息 and/or 缩略图。然后您可以在那里更新您的视图。

  2. 另一种方法是不创建任何 xml,而是在自定义 ViewGroup 构造函数中以编程方式创建它们。然后你可以使用与上面类似的辅助方法来为各种视图组件设置值。设置完所有内容后,最后调用 requestLayout() 。然后,如果需要,您可以更新 onLayout() 中的视图并执行任何 spacing/margin 计算。然后使用这些值将它们绘制在 onDraw() 中。

与自定义可绘制对象相比,我更喜欢使用自定义视图。因为它可以灵活地测量和自定义高度和宽度。

所以我创建了 FileView:

import android.content.Context
import android.content.res.Resources
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.text.TextPaint
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageView

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

    init {
        setImageResource(R.drawable.ic_file)
    }

    var icon: Drawable? = null
        set(value) {
            field = value
            postInvalidate()
        }

    var ext: CharSequence? = null
        set(value) {
            field = value
            postInvalidate()
        }

    private val iconRect = Rect()
    private val extRect = Rect()
    private val extPaint by lazy {
        TextPaint().apply {
            style = Paint.Style.FILL
            color = Color.WHITE
            isAntiAlias = true
            textAlign = Paint.Align.CENTER
            textSize = 12f * Resources.getSystem().displayMetrics.density + 0.5f
        }
    }
    private val extBackgroundPaint by lazy {
        TextPaint().apply {
            style = Paint.Style.FILL
            color = Color.BLACK
            isAntiAlias = true
        }
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        val centerX = width / 2
        val centerY = height / 2

        icon?.let { icon ->
            iconRect.set(
                centerX - icon.intrinsicWidth / 2,
                centerY - icon.intrinsicHeight / 2,
                centerX + icon.intrinsicWidth / 2,
                centerY + icon.intrinsicHeight / 2
            )

            icon.bounds = iconRect
            icon.draw(canvas)
        }


        ext?.let { ext ->
            val truncatedExt =
                if (ext.length > 6) ext.subSequence(0, 6).toString().plus('…')
                else ext

            // extRect is used for measured ext height
            extPaint.getTextBounds("X", 0, 1, extRect)
            val extHeight = extRect.height() // keep ext height
            val extWidth = extPaint.measureText(truncatedExt, 0, truncatedExt.length).toInt() // keep ext width

            val extPadding = 4.toPx
            val extMargin = 4.toPx

            val extRight = width - extMargin
            val extBottom = height - extMargin
            // extRect is reused for ext background bound
            extRect.set(
                extRight - extWidth - extPadding * 2,
                extBottom - extHeight - extPadding * 2,
                extRight,
                extBottom
            )
            canvas.drawRect(extRect, extBackgroundPaint)

            canvas.drawText(
                truncatedExt,
                0,
                truncatedExt.length,
                extRect.exactCenterX(),
                extRect.bottom - ((extRect.height() - extHeight) / 2f),
                extPaint
            )
        }
    }

    private val Int.toPx get() = (this * Resources.getSystem().displayMetrics.density).toInt()
}

并使用它:

with(binding.fileView) {
    icon = ContextCompat.getDrawable(context, R.drawable.ic_music)
    ext = ".aiff"
}

输出: