I current have a custom view that I override the onDraw and draw an arc. I want to draw text within this arc. To do this, I use drawTextOnPath and this display curved text at the top of the arc. However, sometimes the text is quite long, so I want to allow it to go on to multiple lines.
I currently use code like this to draw on to multiple lines: -
textView.getPaint().getTextBounds(s, 0,
s.length(), r);
int yOffset=r.height() + textSpacing;
int textStart=0;
int numberOfLines= (int) (r.width()/arcWidth) + 1;
for (int i=0; i < numberOfLines; i ++) {
canvas.drawTextOnPath(s.substring(textStart, textStart + s.length() / numberOfLines),
childHolder.path, 0, yOffset, paint);
yOffset+=r.height() +textSpacing;
textStart=s.length()/numberOfLines;
}
However, this obviously doesn't take into account how wide the text is further down the arc. Is there a way of doing this with using something like staticlayout/dynamiclayout (text does change a lot).
If anyone could point me in either something in android SDK I can use, or the maths to calculate the available width
This bit of code solved my issue: -
PathMeasure pm = new PathMeasure(path, false);
layout = new DynamicLayout(spannableText, spannableText, paint, (int) arcPathMeasure.getLength(), Layout.Alignment.ALIGN_CENTER, 1, 0, true);
Thanks pskink!
I'm trying to draw a text to the screen and then have a rectangle in the same spot to account for knowing when the user clicks within it (and clicks the text). Problem is when I put the text in one spot on the screen and the rectangle in the same spot, it apparently isn't in the same spot. Is there some setting I need to set somewhere?
private final String[] options = {"Start", "High Scores", "Help", "Quit"};
public void draw(final Canvas g)
{
for (int i = 0; i < options.length; i++)
{
width = HORIZONTALOFFSET * 3 + spaceInvadersTitle.getWidth();
height = GamePanel.getScreenHeight() / 4 - (75 * 2 + TEXT_SIZE) / 2 + i * 75;
rect[i] = new Rect(width, height, width + 325, height + TEXT_SIZE);
g.drawRect(rect[i], paint);
g.drawText(options[i], width, height, paint);
}
}
FYI "width" and "height" are more like x- and y-coords. The horizontal alignment is not the problem - just the vertical. If you'll notice from the code, I'm setting the starting x- and y-coordinates for the drawText and drawRect the same exact position, but that's not how they're showing on the screen. Instead it seems the drawText is using that position as a lowerleft anchorpoint or something like that. Is that how it works? If so, how do I change that?
Also, if you have any suggestions on how to approach listening for when the user clicks on a drawn text, I'm all ears. This is the easiest way I could think of, and how I do it in regular desktop Java.
I have the following code to find the screen dimensions calculate how much text fits on the screen.
Now I need to know how many lines fit on the screen. I have the size of the text from the ascent to the descent. I need to know the height of the line spacing. getFontSpacing also gives the value from the ascent to the descent.
Does anyone know if there is a way of finding the line spacing's value?
//Code for Getting screen Dimensions
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.x;
int height = size.y;
//Breaking Text When Width Is Filled
Paint p = new Paint();
p.setTextSize(60);
int textNum = p.breakText(sText, 0, 20, forOrBack, width, null);
//Working Out How Many Lines Can Be Entered In The Screen
float fHeight = p.descent() - p.ascent();
int tHeight = (int) fHeight;
int numLines = height/tHeight;
The following will return the recommended line spacing
textView.getPaint().getFontSpacing()
you can get the height of you text several ways. I prefer paint's getTextBounds method and just add the two values together
For line spacing use textView.getLineHeight();
reference : http://developer.android.com/reference/android/widget/TextView.html#getLineHeight()
Paint.FontMetrics fm = paint.getFontMetrics();
float fullHeight = fm.top - fm.bottom;
As far as I remember, fm.bottom < 0, therefore by subtracting you actually get
Math.abs(fm.top) + Math.abs(fm.bottom)
This value is bigger than ascent-descent, and, I guess, it is the actual line size.
You may consider using TextView.getLineHeight() after setting TextSize and preferebly after layout is complete
(e.g in OnWindowFocusChanged).
I need to get a ascender/descender and x-height..
By using following code I can find the descender and the total height:
descender_height = paint.descent();
total_height = descender_height - paint.ascent();
//ascender = ?; is this always equal to descender height?
//x_height = ?; total_height - 2*descender_height ?
Thanks
I would think the ascender and descender height would typically be the same, but I wouldn't depend on it for every font. I don't really see a direct way to get to the x-height, but a trick you could use would be something like the below. Also, for the total height, are you talking about the absolute distance from the highest ascender to the lowest descender? I've also included something for that below. I haven't tested these myself, but it should work (but let me know if I'm misinterpreting something you've said):
// Assuming TextPaint/Paint tp;
Rect bounds;
// this will just retrieve the bounding rect for 'x'
tp.getTextBounds("x", 0, 1, bounds);
int xHeight = bounds.height();
Paint.FontMetrics metrics = tp.getFontMetrics();
int totalHeight = metrics.top - metrics.bottom;
This is what worked for me:
Paint.FontMetrics fm = paint.getFontMetrics();
int totalHeight = (int)(fm.bottom - fm.top + .5f);
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();
}
}