带有动态文本的自定义图像,顶部带有背景
Custom image with dynamic text on top with background
我正在尝试在 Kotlin 中创建一个自定义的 ImageView
或 Drawable
,这样可以在运行时在基本图像上绘制动态文件扩展名。最终结果将如下所示。尝试创建自定义 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,相关代码很容易找到。
更简单的方法是在您的 xml 中设计此布局。然后创建您的自定义 class 扩展 ViewGroup 并在其构造函数中您可以膨胀视图 xml 并初始化事物。然后你可以定义任何辅助方法,比如 setData(),你可以在其中传递文件扩展名信息 and/or 缩略图。然后您可以在那里更新您的视图。
另一种方法是不创建任何 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"
}
输出:
我正在尝试在 Kotlin 中创建一个自定义的 ImageView
或 Drawable
,这样可以在运行时在基本图像上绘制动态文件扩展名。最终结果将如下所示。尝试创建自定义 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,相关代码很容易找到。
更简单的方法是在您的 xml 中设计此布局。然后创建您的自定义 class 扩展 ViewGroup 并在其构造函数中您可以膨胀视图 xml 并初始化事物。然后你可以定义任何辅助方法,比如 setData(),你可以在其中传递文件扩展名信息 and/or 缩略图。然后您可以在那里更新您的视图。
另一种方法是不创建任何 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"
}
输出: