Align text around ImageSpan center vertical

前端 未结 9 1865
抹茶落季
抹茶落季 2020-11-28 23:59

I have an ImageSpan inside of a piece of text. What I\'ve noticed is that the surrounding text is always drawn at the bottom of the text line -- to be more precise, the size

9条回答
  •  难免孤独
    2020-11-29 00:22

    This solution provides a vertical centering based on actual letter size. It supports centering using capital letters and lower-case letters. For example, look at the marker character near a letter: X•. This solution achieves a similar effect.

    This is a modified version of @WindRider's answer. Also, it's in Kotlin. And it supports drawable size customization.

    The reason why this solution is created is to provide a better visual result. A lot of other solutions use font ascent. But it appears to cause visual problems in some cases. Android's default Roboto font, for example, has ascent higher than a typical capital letter top border. And because of it, some manual adjustments were needed to properly center an image.

    class CenteredImageSpan(context: Context,
                            drawableRes: Int,
                            private val centerType: CenterType = CenterType.CAPITAL_LETTER,
                            private val customHeight: Int? = null,
                            private val customWidth: Int? = null) : ImageSpan(context, drawableRes) {
    
        private var mDrawableRef: WeakReference? = null
    
        override fun getSize(paint: Paint, text: CharSequence,
                             start: Int, end: Int,
                             fontMetrics: FontMetricsInt?): Int {
    
            if (fontMetrics != null) {
                val currentFontMetrics = paint.fontMetricsInt
                // keep it the same as paint's Font Metrics
                fontMetrics.ascent = currentFontMetrics.ascent
                fontMetrics.descent = currentFontMetrics.descent
                fontMetrics.top = currentFontMetrics.top
                fontMetrics.bottom = currentFontMetrics.bottom
            }
    
            val drawable = getCachedDrawable()
            val rect = drawable.bounds
            return rect.right
        }
    
        override fun draw(canvas: Canvas,
                          text: CharSequence,
                          start: Int,
                          end: Int,
                          x: Float,
                          lineTop: Int,
                          baselineY: Int,
                          lineBottom: Int,
                          paint: Paint) {
            val cachedDrawable = getCachedDrawable()
            val drawableHeight = cachedDrawable.bounds.height()
    
            val relativeVerticalCenter = getLetterVerticalCenter(paint)
    
            val drawableCenter = baselineY + relativeVerticalCenter
            val drawableBottom = drawableCenter - drawableHeight / 2
    
            canvas.save()
            canvas.translate(x, drawableBottom.toFloat())
            cachedDrawable.draw(canvas)
            canvas.restore()
        }
    
        private fun getLetterVerticalCenter(paint: Paint): Int =
             when (centerType) {
                CenterType.CAPITAL_LETTER -> getCapitalVerticalCenter(paint)
                CenterType.LOWER_CASE_LETTER -> getLowerCaseVerticalCenter(paint)
            }
    
        private fun getCapitalVerticalCenter(paint: Paint): Int {
            val bounds = Rect()
            paint.getTextBounds("X", 0, 1, bounds)
            return (bounds.bottom + bounds.top) / 2
        }
    
        private fun getLowerCaseVerticalCenter(paint: Paint): Int {
            val bounds = Rect()
            paint.getTextBounds("x", 0, 1, bounds)
            return (bounds.bottom + bounds.top) / 2
        }
    
    
        // Redefined here because it's private in DynamicDrawableSpan
        private fun getCachedDrawable(): Drawable {
    
            val drawableWeakReference = mDrawableRef
            var drawable: Drawable? = null
            if (drawableWeakReference != null) drawable = drawableWeakReference.get()
            if (drawable == null) {
                drawable = getDrawable()!!
    
                val width = customWidth ?: drawable.intrinsicWidth
                val height = customHeight ?: drawable.intrinsicHeight
    
                drawable.setBounds(0, 0,
                                   width, height)
                mDrawableRef = WeakReference(drawable)
            }
            return drawable
    
        }
    
        enum class CenterType {
            CAPITAL_LETTER, LOWER_CASE_LETTER
        }
    
    }
    

提交回复
热议问题