Truly aligning text in Android TextView

I'm trying to display a TextView in Android so that the text in the view is top-aligned:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Create container layout
    FrameLayout layout = new FrameLayout(this);

    // Create text label
    TextView label = new TextView(this);
    label.setTextSize(TypedValue.COMPLEX_UNIT_PX, 25);     // 25 pixels tall
    label.setGravity(Gravity.TOP + Gravity.CENTER);        // Align text top-center
    label.setPadding(0, 0, 0, 0);                          // No padding
    Rect bounds = new Rect();
    label.getPaint().getTextBounds("gdyl!", 0, 5, bounds); // Measure height
    label.setText("good day, world! "+bounds.top+" to "+bounds.bottom);
    label.setTextColor      (0xFF000000);                  // Black text
    label.setBackgroundColor(0xFF00FFFF);                  // Blue background

    // Position text label
    FrameLayout.LayoutParams layoutParams = 
            new FrameLayout.LayoutParams(300, 25, Gravity.LEFT + Gravity.TOP);
                                                            // also 25 pixels tall
    layoutParams.setMargins(50, 50, 0, 0);
    label.setLayoutParams(layoutParams);

    // Compose screen
    layout.addView(label);
    setContentView(layout);
}

      

This code outputs the following image:

enter image description here

Points to note:

  • The blue box is 25px high, just like the request
  • Text borders are also reported as 25px high as requested (6 - (-19) = 25)
  • The text does not start at the top of the label, but has a caption above it, ignoring setPadding ()
  • This causes the text to be clipped at the bottom, although the box is technically high

How do I tell the TextView to start text at the very top of the window?

I have two restrictions on the possible answers:

  • I need the text to be top-aligned, so if there is some trick to bottom-align or center it vertically, I cannot use it as I have scripts where the TextView is taller than it should to be.
  • I'm a bit like compatibility, so if possible I would like to stick with calls that were available in early Android APIs (preferably 1, but definitely not higher than 7).
+3


source to share


2 answers


TextViews use the abstract class android.text.Layout to draw text on the canvas:

canvas.drawText(buf, start, end, x, lbaseline, paint);

The vertical offset is lbaseline

calculated as the bottom of the line minus the font descent:

int lbottom = getLineTop(i+1);


int lbaseline = lbottom - getLineDescent(i);

The two callable functions getLineTop

and getLineDescent

are abstract, but a simple implementation can be found in BoringLayout (go figure ... :), which simply returns its values ​​for mBottom

and mDesc

. They are calculated in the init method as follows:

if (includepad) {
    spacing = metrics.bottom - metrics.top;
} else {
    spacing = metrics.descent - metrics.ascent;
}

if (spacingmult != 1 || spacingadd != 0) {
    spacing = (int)(spacing * spacingmult + spacingadd + 0.5f);
}

mBottom = spacing;

if (includepad) {
    mDesc = spacing + metrics.top;
} else {
    mDesc = spacing + metrics.ascent;
}

      

Here includepad

is a boolean value that indicates whether the text should include additional padding to display glyphs that pass in the specified ascent. It can be set (as @ggc pointed out) using the TextView's setIncludeFontPadding method .



If the includesepad parameter is set to true (the default), the text is positioned with the base value specified by the top

-field
of the metric font. Otherwise, the baseline of the text will be taken from descent

-field
.

So, technically, this should mean that all we have to do is disable IncludeFontPadding, but unfortunately this gives the following output:

enter image description here

The reason for this is that the font reports -23.2 as a bubble, and the bounding box reports an upper value of -19. I don't know if this is a "bug" in the font or if it should be. Unfortunately, FontMetrics does not provide any value that matches the 19 given in the bounding box, even if you try to somehow include the 240dpi screen resolution message versus 72dpi font point detection, so there is no "official" way to fix this ...

But, of course, the available information can be used to crack the solution. This can be done in two ways:

  • with IncludeFontPadding left alone, i.e. set to true:

    double top = label.getPaint().getFontMetrics().top;
    label.setPadding(0, (int) (top - bounds.top - 0.5), 0, 0);
    
          

    i.e. the vertical padding is set to compensate for the difference in the y-value specified in the text borders and the upper-font value. Result:

    enter image description here

  • with IncludeFontPadding set to false:

    double ascent = label.getPaint().getFontMetrics().ascent;
    label.setPadding(0, (int) (ascent - bounds.top - 0.5), 0, 0);
    label.setIncludeFontPadding(false);
    
          

    i.e. vertical padding is set to compensate for differences in the y-value specified in the text boundaries and the ascending font-value. Result:

    enter image description here

Note that there is nothing magical about IncludeFontPadding being false. Both versionsmust work. The reason they give different results is slightly different rounding errors when floating point font values ​​are converted to integers. It just so happens that it looks better in this particular case if IncludeFontPadding is set to false, but this may be different for different fonts or font sizes. It is probably quite easy to tweak the top padding calculation to get the same exact rounding errors as the calculation used by BoringLayout. I haven't done that yet, as I'm more likely to use an error-free font instead, but I can add it later if I find it for a while. Then it should really be inappropriate whether the IncludeFontPadding is set to false or true.

+8


source


If your TextView is inside a different layout, make sure there is enough space between them. You can add indentation at the bottom of the parent view and see if you have the full text. It worked for me!

Example: you have a textView inside a FrameLayout, but the FrameLayout is too small and shrinks your textView. Add padding to your FrameLayout to see if it works.

Edit : change this line

     FrameLayout.LayoutParams layoutParams = 
            new FrameLayout.LayoutParams(300, 25, Gravity.LEFT + Gravity.TOP);

      

for this line



    FrameLayout.LayoutParams layoutParams = 
            new FrameLayout.LayoutParams(300, 50, Gravity.LEFT + Gravity.TOP);

      

This will make the window larger and, in the same way, provide enough space for your text.

OR add this line

     label.setIncludeFontPadding(false);

      

This will remove the surrounding font and allow the text to be seen. But the only thing that doesn't work in your case is that it won't show completely the letters like "g" that go under the line ... You may need to resize the window or text a bit (like 2-3), if you really want it to work.

+1


source







All Articles