有时候我们会接到一个这样的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
的绘制区域为top
至buttom
。 为false
时,TextView
的绘制区域为ascent
至decent
。
那么就好办了 也就是说 我们把ascent
下移到文字的上边缘 把decent
上移到文字的下边缘 然后再借助android:includeFontPadding="false"
就可以把文字的上下边距去掉了
接下来就有两个问题
怎么获取文字的上边缘和下边缘
怎么修改ascen
t和decent
val rect = Rect()
paint.getTextBounds(text.toString(), 0, text.length, rect)
paint.getTextBounds
可以获取文字的left
top
right
bottom
对应着文字的上下左右边缘
注意:top
和ascent
一样 一般为负值 baseline
往上的为负 往下的为正 bottom
和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;
**********
}
修改变量ascent
和decent
接下来的问题就是 这么把我们的LineHeightSpan
设置到Textview
上呢
android.text 里面有个叫SpannableStringBuilder
的类 主要就是给我们用来修改textview
文字样式的
我们只需要SpannableStringBuilder
调用里面的setSpan
方法 把我们的LineHeightSpan
设置进去
然后在Textview.setText
的时候把我们的SpannableStringBuilder
放进去就可以了
现在我们有
Rect
知道了文字的上边缘和下边缘。用FontMetricsInt
把ascent
和decent
移动到文字的边缘 然后把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