I want to calculate the line (or layout) height (in DP) which contains only TextView as outcome of the TextView text size when using default line spacing ?
I.E. for this layout :
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<TextView
android:id="#+id/minRow1col1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textIsSelectable="false"
android:textSize="11dp" />
</LinearLayout>
What will be the layout/line height ? (any formula ? I don't need the value in run time)
Thanks !
I don't know if this help you guys, but my solution to get the height of a line it's independent of the height of the layout, just take the font metrics like this:
myTextView.getPaint().getFontMetrics().bottom - myTextView.getPaint().getFontMetrics().top)
With this you will get the line height and for me, it's works with all words ( there are some chars like "g" or "j" that take some bottom space, the so called "descender" and so on ).
Try using the TextPaint object of TextView.
TextView tv = useTextView;
String text = tv.getText().toString();
Paint textPaint = tv.getPaint();
Rect textRect = new Rect();
textPaint.getTextBounds(text, 0, text.length(), textRext);
int textHeight = textRect.height();
Per documentation of Paint#getTextBound:
Return in bounds (allocated by the caller) the smallest rectangle that
encloses all of the characters, with an implied origin at (0,0).
Using the Paint object that the TextView uses will ensure it has the same parameters set that will be used to draw the text.
You have to create custom Textview and use getActualHeight() method. Where the formula is: actualHeight=(int) ((getLineCount()-1)*getTextSize());
public class TextViewHeightPlus extends TextView {
private static final String TAG = "TextView";
private int actualHeight=0;
public int getActualHeight() {
return actualHeight;
}
public TextViewHeightPlus(Context context) {
super(context);
}
public TextViewHeightPlus(Context context, AttributeSet attrs) {
super(context, attrs);
setCustomFont(context, attrs);
}
public TextViewHeightPlus(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
actualHeight=0;
actualHeight=(int) ((getLineCount()-1)*getTextSize());
}
}
You can try Paint.getTextBounds():
String finalVal ="Hello";
Paint paint = new Paint();
paint.setTextSize(18);
paint.setTypeface(Typeface.SANS_SERIF);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.FILL);
Rect result = new Rect();
// Measure the text rectangle to get the height
paint.getTextBounds(finalVal, 0, finalVal.length(), result);
Log.d("WIDTH :", String.valueOf(result.width()));
Log.d("HEIGHT :", String.valueOf(result.height()));
Source.
For my usecase I started out by assuming that the line height is a linear function that depends on the textsize, something like:
line_height = text_size*some_constant
Now some_constant is probably a function as well, that depends on what font you use. But since in my requirements the font was static, I was able to calculate some_constant and use repeatedly in a safe way.
To clue you in a little a bit more on my usecase, I was scaling a piece of multiline text in order to fit it in a box of variable height.
In my usecase I wanted to go a step further and included the space multiplier. It was quite simple, as it was just another factor in the equation:
line_height = text_size*some_constant*spacing_multiplier
In summary if you stick to the same font and you calculate the value of some_constant once, you can (probably) get your function.
DISCLAIMER: I say "probably" a lot because I haven't tested a lot of my assumptions, but like I said, it worked for a decently complex usecase.
Related
How can we achieve the fade-out effect on the last line of a TextView, like in the "WHAT'S NEW" section in the Play Store app?
That fade effect can be accomplished by subclassing a TextView class to intercept its draw, and doing something like what the View class does to fade out edges, but only in the last stretch of the final text line.
In this example, we create a unit horizontal linear gradient that goes from transparent to solid black. As we prepare to draw, this unit gradient is scaled to a length calculated as a simple fraction of the TextView's final line length, and then positioned accordingly.
An off-screen buffer is created, and we let the TextView draw its content to that. We then draw the fade gradient over it with a transfer mode of PorterDuff.Mode.DST_OUT, which essentially clears the underlying content to a degree relative to the gradient's opacity at a given point. Drawing that buffer back on-screen results in the desired fade, no matter what is in the background.
public class FadingTextView extends AppCompatTextView {
private static final float FADE_LENGTH_FACTOR = .4f;
private final RectF drawRect = new RectF();
private final Rect realRect = new Rect();
private final Path selection = new Path();
private final Matrix matrix = new Matrix();
private final Paint paint = new Paint();
private final Shader shader =
new LinearGradient(0f, 0f, 1f, 0f, 0x00000000, 0xFF000000, Shader.TileMode.CLAMP);
public FadingTextView(Context context) {
this(context, null);
}
public FadingTextView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
}
public FadingTextView(Context context, AttributeSet attrs, int defStyleAttribute) {
super(context, attrs, defStyleAttribute);
paint.setShader(shader);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
}
#Override
protected void onDraw(Canvas canvas) {
// Locals
final RectF drawBounds = drawRect;
final Rect realBounds = realRect;
final Path selectionPath = selection;
final Layout layout = getLayout();
// Figure last line index, and text offsets there
final int lastLineIndex = getLineCount() - 1;
final int lastLineStart = layout.getLineStart(lastLineIndex);
final int lastLineEnd = layout.getLineEnd(lastLineIndex);
// Let the Layout figure a Path that'd cover the last line text
layout.getSelectionPath(lastLineStart, lastLineEnd, selectionPath);
// Convert that Path to a RectF, which we can more easily modify
selectionPath.computeBounds(drawBounds, false);
// Naive text direction determination; may need refinement
boolean isRtl =
layout.getParagraphDirection(lastLineIndex) == Layout.DIR_RIGHT_TO_LEFT;
// Narrow the bounds to just the fade length
if (isRtl) {
drawBounds.right = drawBounds.left + drawBounds.width() * FADE_LENGTH_FACTOR;
} else {
drawBounds.left = drawBounds.right - drawBounds.width() * FADE_LENGTH_FACTOR;
}
// Adjust for drawables and paddings
drawBounds.offset(getTotalPaddingLeft(), getTotalPaddingTop());
// Convert drawing bounds to real bounds to determine
// if we need to do the fade, or a regular draw
drawBounds.round(realBounds);
realBounds.offset(-getScrollX(), -getScrollY());
boolean needToFade = realBounds.intersects(getTotalPaddingLeft(), getTotalPaddingTop(),
getWidth() - getTotalPaddingRight(), getHeight() - getTotalPaddingBottom());
if (needToFade) {
// Adjust and set the Shader Matrix
final Matrix shaderMatrix = matrix;
shaderMatrix.reset();
shaderMatrix.setScale(drawBounds.width(), 1f);
if (isRtl) {
shaderMatrix.postRotate(180f, drawBounds.width() / 2f, 0f);
}
shaderMatrix.postTranslate(drawBounds.left, drawBounds.top);
shader.setLocalMatrix(shaderMatrix);
// Save, and start drawing to an off-screen buffer
final int saveCount;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
saveCount = canvas.saveLayer(null, null);
} else {
saveCount = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);
}
// Let TextView draw itself to the buffer
super.onDraw(canvas);
// Draw the fade to the buffer, over the TextView content
canvas.drawRect(drawBounds, paint);
// Restore, and draw the buffer back to the Canvas
canvas.restoreToCount(saveCount);
} else {
// Regular draw
super.onDraw(canvas);
}
}
}
This is a drop-in replacement for TextView, and you'd use it in your layout similarly.
<com.example.app.FadingTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#e2f3eb"
android:textColor="#0b8043"
android:lineSpacingMultiplier="1.2"
android:text="#string/umang" />
Notes:
The fade length calculation is based on a constant fraction of the final line's text length, here determined by FADE_LENGTH_FACTOR. This seems to be the same basic methodology of the Play Store component, as the absolute length of the fade appears to vary with line length. The FADE_LENGTH_FACTOR value can be altered as desired.
FadingTextView currently extends AppCompatTextView, but it works perfectly well as a plain TextView, if you should need that instead. I would think that it will work as a MaterialTextView too, though I've not tested that thoroughly.
This example is geared mainly toward relatively plain use; i.e., as a simple wrapped, static label. Though I've attempted to account for and test every TextView setting I could think of that might affect this – e.g., compound drawables, paddings, selectable text, scrolling, text direction and alignment, etc. – I can't guarantee that I've thought of everything.
This is what happens in the preview and on device:
TextView is nothing special, it just loads the custom font:
public class TestTextView extends AppCompatTextView {
public TestTextView(Context context) {
super(context);
init(context);
}
public TestTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public TestTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
void init(Context context) {
Typeface t = Typeface.createFromAsset(context.getAssets(), "fonts/daisy.ttf");
setTypeface(t);
}
}
Layout is also very basic, but just in case:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/material_red200"
android:orientation="vertical">
<*custompackage* .TestTextView
android:gravity="left"
android:padding="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="just some text for testing"
android:textColor="#color/material_black"
android:textSize="100dp" />
</LinearLayout>
As you can see, the left parts, like 'j' and 'f' are cut off.
Setting the padding or margin did not work.
This font fits into it's frame when using from other programs.
Thanks in advance.
Edit:
What #play_err_ mentioned is not a solution in my case.
I am using in the final version a textview that resizes automatically, so adding spaces would be terribly difficult.
I need an explanation why other programs (eg photoshop, after effects...) can calculate a proper bounding box and android cannot
I am also loading different fonts dynamically and I do not want to create an
if(badfont)
addSpaces()
This answer has led me to the right path:
https://stackoverflow.com/a/28625166/4420543
So, the solution is to create a custom Textview and override the onDraw method:
#Override
protected void onDraw(Canvas canvas) {
final Paint paint = getPaint();
final int color = paint.getColor();
// Draw what you have to in transparent
// This has to be drawn, otherwise getting values from layout throws exceptions
setTextColor(Color.TRANSPARENT);
super.onDraw(canvas);
// setTextColor invalidates the view and causes an endless cycle
paint.setColor(color);
System.out.println("Drawing text info:");
Layout layout = getLayout();
String text = getText().toString();
for (int i = 0; i < layout.getLineCount(); i++) {
final int start = layout.getLineStart(i);
final int end = layout.getLineEnd(i);
String line = text.substring(start, end);
System.out.println("Line:\t" + line);
final float left = layout.getLineLeft(i);
final int baseLine = layout.getLineBaseline(i);
canvas.drawText(line,
left + getTotalPaddingLeft(),
// The text will not be clipped anymore
// You can add a padding here too, faster than string string concatenation
baseLine + getTotalPaddingTop(),
getPaint());
}
}
I have encountered the same problem and i found a one liner solution for thouse who are not using the TextView.shadowLayer.
this is based on the source code that [Dmitry Kopytov] brought here:
editTextOrTextView.setShadowLayer(editTextOrTextView.textSize, 0f, 0f, Color.TRANSPARENT)
that's it, now the canvas.clipRect in TextView.onDraw() won't cut off the curly font sides.
Reworked #Dmitry Kopytov solution:
in Kotlin
recycle the old bitmap
added documentation
fall back on default TextView rendering if the bitmap cannot be created (not enough memory)
Code:
/**
* This TextView is able to draw text on the padding area.
* It's mainly used to support italic texts in custom fonts that can go out of bounds.
* In this case, you've to set an horizontal padding (or just end padding).
*
* This implementation is doing a render-to-texture procedure, as such it consumes more RAM than a standard TextView,
* it uses an additional bitmap of the size of the view.
*/
class TextViewNoClipping(context: Context, attrs: AttributeSet?) : AppCompatTextView(context, attrs) {
private class NonClippableCanvas(#NonNull val bitmap: Bitmap) : Canvas(bitmap) {
override fun clipRect(left: Float, top: Float, right: Float, bottom: Float): Boolean {
return true
}
}
private var rttCanvas: NonClippableCanvas? = null
override fun onSizeChanged(width: Int, height: Int,
oldwidth: Int, oldheight: Int) {
if ((width != oldwidth || height != oldheight) && width > 0 && height > 0) {
rttCanvas?.bitmap?.recycle()
try {
Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)?.let {
rttCanvas = NonClippableCanvas(it)
}
} catch (t: Throwable) {
// If for some reasons the bitmap cannot be created, we fall back on default rendering (potentially cropping the text).
rttCanvas?.bitmap?.recycle()
rttCanvas = null
}
}
super.onSizeChanged(width, height, oldwidth, oldheight)
}
override fun onDraw(canvas: Canvas) {
rttCanvas?.let {
// Clear the RTT canvas from the previous font.
it.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
// Draw on the RTT canvas (-> bitmap) that will use clipping on the NonClippableCanvas, resulting in no-clipping
super.onDraw(it)
// Finally draw the bitmap that contains the rendered text (no clipping used here, will display on top of padding)
canvas.drawBitmap(it.bitmap, 0f, 0f, null)
} ?: super.onDraw(canvas) // If rtt is not available, use default rendering process
}
}
I encountered the same problem when I used some fonts in EditText.
My first attempt was to use padding. Size of view increased but text is still cropped.
Then I looked at the source code TextView. In method onDraw method Canvas.clipRect is called to perform this crop.
My solution to bypass cropping when use padding :
1) Сreate custom class inherited from Canvas and override method clipRect
public class NonClippableCanvas extends Canvas {
public NonClippableCanvas(#NonNull Bitmap bitmap) {
super(bitmap);
}
#Override
public boolean clipRect(float left, float top, float right, float bottom) {
return true;
}
}
2) Create custom TextView and override methods onSizeChanged and onDraw.
In the method onSizeChanged create bitmap and canvas.
In the method onDraw draw on bitmap by passing our custom Canvas to method super.onDraw. Next, draw this bitmap on the target canvas.
public class CustomTextView extends AppCompatTextView {
private Bitmap _bitmap;
private NonClippableCanvas _canvas;
#Override
protected void onSizeChanged(final int width, final int height,
final int oldwidth, final int oldheight) {
if (width != oldwidth || height != oldheight) {
_bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
_canvas = new NonClippableCanvas(_bitmap);
}
super.onSizeChanged(width, height, oldwidth, oldheight);
}
#Override
protected void onDraw(Canvas canvas) {
_canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
super.onDraw(_canvas);
canvas.drawBitmap(_bitmap, 0, 0, null);
}
}
A workaround is to add a space before typing. It will save you a lot of coding but will result in a "padding" to the left.
android:text=" text after a space"
replace TextView.BufferType.SPANNABLE with TextView.BufferType.NORMAL
What if you wrap it in another layout and add padding to that? For example something like this:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="24dp">
<*custompackage* .TestTextView
android:gravity="left"
android:padding="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="just some text for testing"
android:textColor="#color/material_black"
android:textSize="100dp" />
</RelativeLayout>
Not having your font and other themes etc I've just tried it with the cursive font for example and on my machine it would look like this.
screenshot
Update:
Looks like you're not the only one to have had this issue and the other answers here and here both unfortunately relate to adding extra spaces.
I've created a bug ticket here since it looks like a bug to me.
I'm trying to draw a number inside of a circle and having issues with the centering on the text. The following is the xml I'm using to display the TextView
<RelativeLayout
android:id="#+id/label"
android:layout_width="72dip"
android:layout_height="72dip"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:layout_margin="8dip" >
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/hole_background"
android:layout_centerInParent="true"/>
<TextView
android:id="#+id/hole"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textColor="#android:color/white"
android:textSize="56sp"
android:textStyle="bold"
android:gravity="center"/>
</RelativeLayout>
The problem is it is just slightly more towards the bottom (by about 10 pixels on the device I tested). This is a screenshot from the view hierarchy viewer that shows what I'm talking about...
I've tried combining the views and using the background such as here, or the removing the font padding like suggested here but neither worked, the font padding ending up as seen below...
I am currently am using a negative margin to adjust it to look right but that doesn't seem like the correct way to do it. Does anyone have any ideas on how to make text centered without using negative margins that I have to manually check based upon the text size?
Thanks in advance
So based upon the comments of LairdPleng and 323go I ended up just creating a custom view. The following view will do the centering exactly based upon the height of the number being drawn...
public class Label extends View {
private static final int TEXT_SIZE = 56;
private String mText;
private float mCenterX;
private float mCenterY;
private float mRadius;
private Paint mCirclePaint;
private Paint mTextPaint;
private Rect mTextBounds;
public HoleLabel(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setupCanvasComponents(context);
}
public HoleLabel(Context context, AttributeSet attrs) {
super(context, attrs);
setupCanvasComponents(context);
}
public HoleLabel(Context context) {
super(context);
setupCanvasComponents(context);
}
public void setText(String text) {
if (!StringUtils.equals(mText, text)) {
mText = text;
invalidate();
}
}
private void setupCanvasComponents(Context context) {
mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mCirclePaint.setColor(Color.BLACK);
mCirclePaint.setStyle(Paint.Style.FILL);
Typeface font = Typeface.createFromAsset(context.getAssets(), "fonts/CustomFont.otf");
DisplayMetrics displayMetrics = new DisplayMetrics();
((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay().getMetrics(displayMetrics);
float scaledDensity = displayMetrics.scaledDensity;
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.LINEAR_TEXT_FLAG);
mTextPaint.setColor(Color.WHITE);
mTextPaint.setStyle(Paint.Style.FILL);
mTextPaint.setTextSize(TEXT_SIZE * scaledDensity);
mTextPaint.setTypeface(font);
mTextPaint.setTextAlign(Align.CENTER);
mTextBounds = new Rect();
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mCenterX = w / 2.0f;
mCenterY = h / 2.0f;
mRadius = w / 2.0f;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Draw the background
canvas.drawCircle(mCenterX, mCenterY, mRadius, mCirclePaint);
// Draw the text
if (mText != null) {
mTextPaint.getTextBounds(mText, 0, mText.length(), mTextBounds);
canvas.drawText(mText, mCenterX, (mCenterY + mTextBounds.height() / 2), mTextPaint);
}
}
}
And in xml...
<com.example.widget.Label
android:id="#+id/label"
android:layout_width="72dip"
android:layout_height="72dip"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:layout_margin="8dip" />
The result being...
I had a very specific use case so I don't know if it'll work well if with less rigid size specifications but it was the way to go for me.
Would add this as a comment, but don't have enough reputation. Expanding on 323go's comment, you might find it easier to use images for your numbers, rather than spending a whole lot of time changing fonts and manually adjusting offsets - which may look fine on one dpi then look off again on another.
I have an xml layout that has 3 input boxes and a 'generate' button.
When the user puts in there values there I'd like to draw a triangle underneath it
I know how to create a new view and go to it, but I'm not sure how to draw it on the same view being that I'm working with an xml view.
Below is a screenshot of what I want to do.
Thank you
http://i.stack.imgur.com/9oBJV.png
You could create a custom view class.
class Triangle extends View {
private int vertexA, vertexB, vertexC;
public Triangle(Context ctx){
this(ctx,null);
}
public Triangle(Context ctx, AttributeSet attrs){
this(ctx,attrs,0);
}
public Triangle(Context ctx, AttributeSet attrs, int defStyle){
super(ctx,attrs,defStyle);
}
public void setSides(int a, int b, int c){
this.vertexA = a;
this.vertexB = b;
this.vertexC = c;
this.invalidate();
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Try for a width based on our minimum
int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();
int w = resolveSizeAndState(minw, widthMeasureSpec, 1);
// Whatever the width ends up being, ask for a height that would let the triangle
// get as big as it can
int minh = MeasureSpec.getSize(w) - (int)mTextWidth + getPaddingBottom() + getPaddingTop();
int h = resolveSizeAndState(MeasureSpec.getSize(w) - (int)mTextWidth, heightMeasureSpec, 0);
setMeasuredDimension(w, h);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
Path path = new Path();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.TRANSPARENT);
c.drawPaint(paint);
// start the path at the "origin"
path.MoveTo(10,10); // origin
// add a line for side A
path.lineTo(10,this.vertexA);
// add a line for side B
path.lineTo(this.vertexB,10);
// close the path to draw the hypotenuse
path.close();
paint.setStrokeWidth(3);
paint.setPathEffect(null);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
c.drawPath(path, paint);
}
}
Note that I've hard coded the origin (the bottom left corner - the right angle) and only drawn 2 sides, since the hypotenuse is drawn by the closed path (this saves doing any extra maths). You'll want to play with onMeasure and scale your triangle as you need. Something like this:
path.lineTo(10, this.vertexA * yScale);
path.lineTo(this.vertexB * xScale ,10);
You're activity should check that the 3 values do indeed represent the sides of a right angled triangle, then call setSides(). I've added all 3 sides, although we are only using a and b. You could remove c if you prefer.
Please note that this is not copy/paste code. You will need to adapt it but it should give you a head start. Good luck.
Just put your custom view into the layout below the button. The exact xml to use depends on the type of your top level view container (which is probably a RelativeLayout).
To make it invisible at first you can set its visibility to INVISIBLE. When it should appear set the visibility to VISIBLE.
Any straight forward way to measure the height of text?
The way I am doing it now is by using Paint's measureText() to get the width, then by trial and error finding a value to get an approximate height. I've also been messing around with FontMetrics, but all these seem like approximate methods that suck.
I am trying to scale things for different resolutions. I can do it, but I end up with incredibly verbose code with lots of calculations to determine relative sizes. I hate it! There has to be a better way.
There are different ways to measure the height depending on what you need.
#1 getTextBounds
If you are doing something like precisely centering a small amount of fixed text, you probably want getTextBounds. You can get the bounding rectangle like this
Rect bounds = new Rect();
mTextPaint.getTextBounds(mText, 0, mText.length(), bounds);
int height = bounds.height();
As you can see for the following images, different strings will give different heights (shown in red).
These differing heights could be a disadvantage in some situations when you just need a constant height no matter what the text is. See the next section.
#2 Paint.FontMetrics
You can calculate the hight of the font from the font metrics. The height is always the same because it is obtained from the font, not any particular text string.
Paint.FontMetrics fm = mTextPaint.getFontMetrics();
float height = fm.descent - fm.ascent;
The baseline is the line that the text sits on. The descent is generally the furthest a character will go below the line and the ascent is generally the furthest a character will go above the line. To get the height you have to subtract ascent because it is a negative value. (The baseline is y=0 and y descreases up the screen.)
Look at the following image. The heights for both of the strings are 234.375.
If you want the line height rather than just the text height, you could do the following:
float height = fm.bottom - fm.top + fm.leading; // 265.4297
These are the bottom and top of the line. The leading (interline spacing) is usually zero, but you should add it anyway.
The images above come from this project. You can play around with it to see how Font Metrics work.
#3 StaticLayout
For measuring the height of multi-line text you should use a StaticLayout. I talked about it in some detail in this answer, but the basic way to get this height is like this:
String text = "This is some text. This is some text. This is some text. This is some text. This is some text. This is some text.";
TextPaint myTextPaint = new TextPaint();
myTextPaint.setAntiAlias(true);
myTextPaint.setTextSize(16 * getResources().getDisplayMetrics().density);
myTextPaint.setColor(0xFF000000);
int width = 200;
Layout.Alignment alignment = Layout.Alignment.ALIGN_NORMAL;
float spacingMultiplier = 1;
float spacingAddition = 0;
boolean includePadding = false;
StaticLayout myStaticLayout = new StaticLayout(text, myTextPaint, width, alignment, spacingMultiplier, spacingAddition, includePadding);
float height = myStaticLayout.getHeight();
What about paint.getTextBounds() (object method)
#bramp's answer is correct - partially, in that it does not mention that the calculated boundaries will be the minimum rectangle that contains the text fully with implicit start coordinates of 0, 0.
This means, that the height of, for example "Py" will be different from the height of "py" or "hi" or "oi" or "aw" because pixel-wise they require different heights.
This by no means is an equivalent to FontMetrics in classic java.
While width of a text is not much of a pain, height is.
In particular, if you need to vertically center-align the drawn text, try getting the boundaries of the text "a" (without quotes), instead of using the text you intend to draw.
Works for me...
Here's what I mean:
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.LINEAR_TEXT_FLAG);
paint.setStyle(Paint.Style.FILL);
paint.setColor(color);
paint.setTextAlign(Paint.Align.CENTER);
paint.setTextSize(textSize);
Rect bounds = new Rect();
paint.getTextBounds("a", 0, 1, bounds);
buffer.drawText(this.myText, canvasWidth >> 1, (canvasHeight + bounds.height()) >> 1, paint);
// remember x >> 1 is equivalent to x / 2, but works much much faster
Vertically center aligning the text means vertically center align the bounding rectangle - which is different for different texts (caps, long letters etc). But what we actually want to do is to also align the baselines of rendered texts, such that they did not appear elevated or grooved. So, as long as we know the center of the smallest letter ("a" for example) we then can reuse its alignment for the rest of the texts. This will center align all the texts as well as baseline-align them.
The height is the text size you have set on the Paint variable.
Another way to find out the height is
mPaint.getTextSize();
You could use the android.text.StaticLayout class to specify the bounds required and then call getHeight(). You can draw the text (contained in the layout) by calling its draw(Canvas) method.
You can simply get the text size for a Paint object using getTextSize() method.
For example:
Paint mTextPaint = new Paint (Paint.ANTI_ALIAS_FLAG);
//use densityMultiplier to take into account different pixel densities
final float densityMultiplier = getContext().getResources()
.getDisplayMetrics().density;
mTextPaint.setTextSize(24.0f*densityMultiplier);
//...
float size = mTextPaint.getTextSize();
You must use Rect.width() and Rect.Height() which returned from getTextBounds() instead. That works for me.
If anyone still has problem, this is my code.
I have a custom view which is square (width = height) and I want to assign a character to it. onDraw() shows how to get height of character, although I'm not using it. Character will be displayed in the middle of view.
public class SideBarPointer extends View {
private static final String TAG = "SideBarPointer";
private Context context;
private String label = "";
private int width;
private int height;
public SideBarPointer(Context context) {
super(context);
this.context = context;
init();
}
public SideBarPointer(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
init();
}
public SideBarPointer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.context = context;
init();
}
private void init() {
// setBackgroundColor(0x64FF0000);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
height = this.getMeasuredHeight();
width = this.getMeasuredWidth();
setMeasuredDimension(width, width);
}
protected void onDraw(Canvas canvas) {
float mDensity = context.getResources().getDisplayMetrics().density;
float mScaledDensity = context.getResources().getDisplayMetrics().scaledDensity;
Paint previewPaint = new Paint();
previewPaint.setColor(0x0C2727);
previewPaint.setAlpha(200);
previewPaint.setAntiAlias(true);
Paint previewTextPaint = new Paint();
previewTextPaint.setColor(Color.WHITE);
previewTextPaint.setAntiAlias(true);
previewTextPaint.setTextSize(90 * mScaledDensity);
previewTextPaint.setShadowLayer(5, 1, 2, Color.argb(255, 87, 87, 87));
float previewTextWidth = previewTextPaint.measureText(label);
// float previewTextHeight = previewTextPaint.descent() - previewTextPaint.ascent();
RectF previewRect = new RectF(0, 0, width, width);
canvas.drawRoundRect(previewRect, 5 * mDensity, 5 * mDensity, previewPaint);
canvas.drawText(label, (width - previewTextWidth)/2, previewRect.top - previewTextPaint.ascent(), previewTextPaint);
super.onDraw(canvas);
}
public void setLabel(String label) {
this.label = label;
Log.e(TAG, "Label: " + label);
this.invalidate();
}
}