Sometimes we’ll get a UI image with text 24px above the parent layout
There’s a problem if we write this on the layout
<TextView
android:id="@+id/textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#2196F3"
android:text="Digital 3 c"
android:textSize="20dp"
android:layout_marginTop="24px"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
Copy the code
We set the top margin to be 24. But it turns out that the actual distance from the text is greater than 24
So what do you do
Most people would say add this attribute android: includeFontPadding = “false”
It looks a little better, but it’s a little off
My solution
Let’s take a look at a few lines drawn by Textview
- Top: The highest point that can be drawn
- Ascent: Recommended upper edge line
- -Leonard: The base line
- Decent: Recommended lower edge line
- Bottom: The lowest point that can be drawn
Say first android: includeFontPadding = “false” this attribute is just put the top down to the ascent, bottom down to there
Then it becomes easy That is to say, We put the ascent, down to the words of the upper edge of the bottom down to the bottom edge of the text Then with the help of android: includeFontPadding = “false” can remove the words on the bottom margin
Then there are two questions
-
How do I get the top and bottom edges of text
-
How to modify ascent and decent
Gets the top and bottom edges of the text
val rect = Rect()
paint.getTextBounds(text.toString(), 0, text.length, rect)
Copy the code
GetTextBounds gets the left top right bottom of the text corresponding to the top, bottom, left, and right edges of the text
12. top and ascent are negative at the same time. 12. bottom and decent are positive at the same time
Modify ascent and decent
Use LineHeightSpan to modify the line height of a Textview
Inherit LineHeightSpan and implement chooseHeight to modify the FontMetricsInt passed by the method
chooseHeight(
text: CharSequence,
start: Int,
end: Int,
spanstartv: Int,
lineHeight: Int,
fm: FontMetricsInt
)
Copy the code
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 intdescent; * * * * * * * * * *}Copy the code
Modify the variables ascent and decent
So the next problem is to set our LineHeightSpan onto our Textview
There is a class called SpannableStringBuilder in Android.text that is used to modify textView text styles
We just need to call the setSpan method in SpannableStringBuilder and set our LineHeightSpan in there
Then we can put our SpannableStringBuilder in textView.settext
Now we have Rect and we know the top and bottom edges of the text. Use FontMetricsInt to move ascent and decent to the edge of the text and set includeFontPadding to True to remove the upper and lower padding
Do you have any questions
If we simply set ascent to top decent at Bottom, we can remove the padding
So if we have two TextViews, one is “one” and the other is “two”, what we found is that the height of the textView varies with the text
Resolve high inconsistencies
We usually set the height of our TextView to wrap_content and we use textSize as the height of our text.
Methods the following
private fun setHeight(fm: FontMetricsInt, rect: Rect) {
// Note: Ascent and top are negative if above the baseline
// Get the height of our text
val textHeight = max(textSize.toInt(), rect.bottom - rect.top)
Return if the size is no larger than the height of text
if (fm.descent - fm.ascent <= textHeight) return
when {
// If ascent has been moved to rect.top then textHeight is the height of descent except ascent
fm.ascent == rect.top -> {
fm.descent = textHeight + fm.ascent
}
// If descent has moved to rect.bottom the height at which Descent is textHeight is the height at which ascent is descent
fm.descent == rect.bottom -> {
fm.ascent = fm.descent - textHeight
}
else- > {/ / otherwise ascent++ downwards along a pixel descent - to move up one pixel
fm.ascent++
fm.descent--
/ / recursion
setHeight(fm, rect)
}
}
}
Copy the code
And just to make it easier for you to understand so I’m doing it recursively up here and I’m doing it non-recursively for the same reason
/ / textview height
val viewHeight = fm.descent - fm.ascent
// The actual height of the text
val textHeight = max(textSize.toInt(), rect.bottom - rect.top)
// The current top margin
val paddingTop = abs(fm.ascent - rect.top)
// The current lower margin
val paddingBottom = fm.descent - rect.bottom
// The minimum distance between the top and bottom
val minPadding = min(paddingTop, paddingBottom)
// The remaining height of the text in the view (textView height - text height) divided by 2 to the average margin height
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
}
}
Copy the code
And then we’ll see what it looks like
Below is the complete code
class ExcludeFontPaddingTextView : AppCompatTextView {
init {
includeFontPadding = false
}
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet? , defStyleAttr:Int) : super(
context,
attrs,
defStyleAttr
)
override fun setText(text: CharSequence? , type:BufferType?). {
super.setText(getCustomText(text), type)
}
private fun getCustomText(text: CharSequence?).: SpannableStringBuilder? {
returntext? .let {val rect = Rect()
paint.getTextBounds(text.toString(), 0, text.length, rect)
val ssb = SpannableStringBuilder(text)
/ / set LineHeightSpan
ssb.setSpan(
object : LineHeightSpan {
override fun chooseHeight(
text: CharSequence,
start: Int,
end: Int,
spanstartv: Int,
lineHeight: Int,
fm: FontMetricsInt
) {
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
)
ssb
}
}
}
Copy the code
You can use it just like a TextView
<com.example.myapplication.ExcludeFontPaddingTextView
android:id="@+id/textview1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#2196F3"
android:text="Ten"
android:textSize="20dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
Copy the code