【TextView】彻底去掉TextView上下内边距Padding的方法

​ 有时候我们会接到一个这样的UI图,文字距离父布局上方是24px

在这里插入图片描述

如果我们在布局上这样写会发现一个问题

<TextView
    android:id="@+id/textview"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="#2196F3"
    android:text="数码3C"
    android:textSize="20dp"
    android:layout_marginTop="24px"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

在这里插入图片描述

我们设置的上边距是24。但是发现实际跟文字之间的距离是大于24的

那么要怎么办呢

大部分人会说加上这个属性android:includeFontPadding="false"

在这里插入图片描述

看起来是好那么一点点,但是还有一点偏差

我的解决办法

我们先来看看Textview绘制的几条线

在这里插入图片描述

  • top:能绘制的最高点
  • ascent:推荐的上边缘线
  • base:基准线
  • decent:推荐的下边缘线
  • bottom:能绘制的最低点

先说说android:includeFontPadding="false" , 这个属性为true时,TextView的绘制区域为topbuttom。 为false时,TextView的绘制区域为ascentdecent

那么就好办了 也就是说 我们把ascent下移到文字的上边缘 把decent上移到文字的下边缘 然后再借助android:includeFontPadding="false"就可以把文字的上下边距去掉了

接下来就有两个问题

  1. 怎么获取文字的上边缘和下边缘

  2. 怎么修改ascent和decent

获取文字的上边缘和下边缘

val rect = Rect()
paint.getTextBounds(text.toString(), 0, text.length, rect)

paint.getTextBounds可以获取文字的left top right bottom 对应着文字的上下左右边缘

注意topascent一样 一般为负值 baseline往上的为负 往下的为正 bottomdecent一般为正

修改ascent和decent

使用LineHeightSpan 可以修改Textview的行高

继承LineHeightSpan后 实现chooseHeight方法就可以修改方法传来的FontMetricsInt

chooseHeight(
    text: CharSequence,
    start: Int,
    end: Int,
    spanstartv: Int,
    lineHeight: Int,
    fm: FontMetricsInt
)

public static class FontMetricsInt {
    
    
  **********
    /**
     * The recommended distance above the baseline for singled spaced text.
     */
    public int   ascent;
    /**
     * The recommended distance below the baseline for singled spaced text.
     */
    public int   descent;
  **********
}

修改变量ascentdecent

接下来的问题就是 这么把我们的LineHeightSpan设置到Textview上呢

android.text 里面有个叫SpannableStringBuilder的类 主要就是给我们用来修改textview文字样式的

我们只需要SpannableStringBuilder 调用里面的setSpan方法 把我们的LineHeightSpan设置进去

然后在Textview.setText的时候把我们的SpannableStringBuilder放进去就可以了

现在我们有Rect知道了文字的上边缘和下边缘。用FontMetricsIntascentdecent移动到文字的边缘 然后把includeFontPadding设置成true就可以让文字的上下padding去掉了

还有问题

如果我们简单粗暴的把ascent设置成top decent设置成bottom 的确能达到去掉上下padding的效果

比如 这两个Textview 一个是 “一” 另一个是 “二” 我们发现 不同文本会导致Textview的高度也一同变化

在这里插入图片描述

解决高度不一致问题

一般Textview的高度我们都是设置成wrap_content的 我们使用textsize作为文字的高度(大部分中文的高度都是一这个大小)

方法如下

private fun setHeight(fm: FontMetricsInt, rect: Rect) {
    
    
  	//注意:如果ascent和top在baseline以上的话 会为负值
  
  	//拿到我们text的高度
    val textHeight = max(textSize.toInt(), rect.bottom - rect.top)
  	//如果修改后的尺寸不大于text的高度了 就返回
    if (fm.descent - fm.ascent <= textHeight) return

    when {
    
    
      	//如果ascent已经移动到了rect.top(文字上边缘)了 那么textHeight除去ascent的高度 就是descent的高度
        fm.ascent == rect.top -> {
    
    
            fm.descent = textHeight + fm.ascent
        }
        //如果descent已经移动到了rect.bottom(文字下边缘)了 那么textHeight除去descent的高度 就是ascent的高度
        fm.descent == rect.bottom -> {
    
    
            fm.ascent = fm.descent - textHeight
        }
        else -> {
    
    
            //其他情况 ascent++往下移移一像素descent--往上移一像素
            fm.ascent++
            fm.descent--
          	//递归
            setHeight(fm, rect)
        }
    }
}

为了方便大家理解 所以上面是用递归的方法 下面是的不递归的方法 目的是一样的

//textview的高度
val viewHeight = fm.descent - fm.ascent
//文字的实际高度 
val textHeight = max(textSize.toInt(),  rect.bottom - rect.top)
//现在的上边距
val paddingTop = abs(fm.ascent - rect.top)
//现在的下边距
val paddingBottom = fm.descent - rect.bottom
//上下边距的最小值
val minPadding = min(paddingTop, paddingBottom)
//文本在view中剩余的高度(textview的高度-文字的高度)除2 的到平均的边距高度
val avgPadding = (viewHeight - textHeight) / 2

when {
    
    
    avgPadding < minPadding -> {
    
    
        fm.ascent += avgPadding
        fm.descent -= avgPadding
    }
    paddingTop < paddingBottom -> {
    
    
        fm.ascent = rect.top
        fm.descent = textHeight + fm.ascent

    }
    else -> {
    
    
        fm.descent = rect.bottom
        fm.ascent = fm.descent - textHeight
    }
}

修改过后我们看看最终效果

在这里插入图片描述

完整的代码

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

    init {
    
    
        includeFontPadding = false
    }


    override fun setText(text: CharSequence?, type: BufferType?) {
    
    
        super.setText(getCustomText(text), type)
    }

    private fun getCustomText(text: CharSequence?): SpannableStringBuilder? {
    
    
        if (text == null) {
    
    
            return null
        }

        return SpannableStringBuilder(text).apply {
    
    
            setSpan(
                object : LineHeightSpan {
    
    
                    override fun chooseHeight(
                        text: CharSequence,
                        start: Int,
                        end: Int,
                        spanstartv: Int,
                        lineHeight: Int,
                        fm: FontMetricsInt
                    ) {
    
    
                        val rect = Rect()
                        paint.getTextBounds(text.toString(), 0, text.length, rect)

                        val viewHeight = fm.descent - fm.ascent
                        val textHeight = max(textSize.toInt(), rect.bottom - rect.top)

                        val paddingTop = abs(fm.ascent - rect.top)
                        val paddingBottom = fm.descent - rect.bottom

                        val minPadding = min(paddingTop, paddingBottom)
                        val avgPadding = (viewHeight - textHeight) / 2

                        when {
    
    
                            avgPadding < minPadding -> {
    
    
                                fm.ascent += avgPadding
                                fm.descent -= avgPadding
                            }
                            paddingTop < paddingBottom -> {
    
    
                                fm.ascent = rect.top
                                fm.descent = textHeight + fm.ascent
                            }
                            else -> {
    
    
                                fm.descent = rect.bottom
                                fm.ascent = fm.descent - textHeight
                            }
                        }
                    }
                },
                0,
                text.length,
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
            )
        }
    }

}

使用方法跟TextView一样

<com.example.myapplication.ExcludeFontPaddingTextView
    android:id="@+id/textview1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="#2196F3"
    android:text="十"
    android:textSize="20dp"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

转载:https://juejin.cn/post/7027050447256944676

猜你喜欢

转载自blog.csdn.net/gqg_guan/article/details/135213432