How can I really center my text horizontally? - android

I have a Piano app with some round labels on it with the note names.
These note names need to be completely centered on the label.
I finally figured out how to do that vertically using ascent and descent. However, I am still not getting the horizontal alignment perfectly ok.
It is mainly about the rendering of single characters. Whatever I do, some of the characters are off. It's slightly, but it's there.
Notice how the E is slightly off to the right for example, and the B a bit to the left.
I am rendering the labels in onDraw(), not with custom views. I also tried with a TextView the size of the labels and using Gravity.CENTER, but this gave the same results. Also note that I tried Align.Center too.
Code:
usedPaint.setTextAlign(Align.LEFT); //Also tried it with Center
Rect textBounds = new Rect();
usedPaint.getTextBounds(infoText, 0, infoText.length(), textBounds);
float textWidth = usedPaint.measureText(infoText);
canvas.drawText(infoText, circleX - (0.5F * textWidth), circleY - ((usedPaint.descent() + usedPaint.ascent())/2),
usedPaint);
canvas.drawRect((circleX - (0.5F* textWidth)), circleY - (0.5F * textBounds.height()), circleX + (0.5F * textWidth), circleY + (0.5F * textBounds.height()), otherPaint);
The drawn box's width is the result of measureText. At the E character you see that somehow it measures some whitespace on the left, making the character drift off to the right.
This is using the standard font on Android 4.0.3. Using a custom TTF font results in the same kind of issues, but different for each character.
I am wondering what else I can do? (Besides getting over it ;) )

with center align just change canvas.drawText(infoText,
circleX - (0.5F * textWidth), ... to canvas.drawText(infoText,
circleX, ...

Related

How to draw a line to perfectly align top of a text?

I am able to align the baseline perfectly. The getLineBounds will give a baseline that is perfectly aligned with text (lineBottom - descent).
I am using rect.top as the topline which will give a line with padding on top see the screenshot.
The main issue is I have different fonts.And this code works perfectly on some fonts.
This is the code
int baseline = getLineBounds(i, rect);
int topLine = rect.top;
canvas.drawLine(rect.left - padding, baseline, rect.right + padding,
baseline, paint);
canvas.drawLine(rect.left - padding, topLine, rect.right + padding, topLine,
paint);
canvas.drawLine(rect.left - padding, (baseline + topLine) / 2, rect.right
+ padding, (baseline + topLine) / 2, paint1);
This is what I have tried.
1) Used a "StaticLayout" to get the top line but didn't make any difference.
2) Used paint to get the height of font and add it with baseline
paint.setTextSize(getTextSize());
paint.setTypeface(getCurrentTypFace());
paint.getTextBounds(getText().toString(), 0, getText().length(), r);
int height = r.height();
int topLine = baseline + height;
3) Tried with FontPadding =false android:includeFontPadding="false"
So my question is how to get the the topline like the baseline.
Any help with this is greatly appreciated.
Try paint.getTextBounds(...) again, but use r.top, (or actually -r.top) instead of r.height:
paint.setTextSize(getTextSize());
paint.setTypeface(getCurrentTypFace());
paint.getTextBounds(getText().toString(), 0, getText().length(), r);
int ascent = r.top;
int topLine = baseline + ascent;
r.height gives the distance from the very bottom of the character to the very top, whereas r.top should give the negative of the distance from the baseline to the very top.
If this doesn't work either, you might just need to draw the text to a temporary canvas in memory and run through the pixels row by row until you find a black one to measure the ascent yourself.
In either case, make sure you use a text that has capital letters in it. If you use a text with only lower case, it will likely give you the coordinates of the orange dashed line instead.
Note that both methods will give you the coordinate of a pixel at the very top edge of the font, not in the center of the stroke. Also, some letters in some fonts might overshoot the coordinate you really want to draw by quite a bit. Your safest bet is probably to pick a standard-string for measuring, like a capital O for the blue line and a lower-case o for the orange line (if it's not a script-font that has a little squiggle in the top right corner of the O that goes above the top of the circle...) and then store some small offset for each font that tells you how thick the line-width of the stroke is, so your lines go through the top of the letter rather than above it.

Render labels in circular path in Android

Render labels in circular path and aligned center to the width of the arc,
Intially, i have tried by point on the circle formula to position the label as below,
label_X_Position = (float) (centreX) - (labelRadius ) * Math.sin(angleForLabel));
label_Y_Position = (float) ((centreY) + (labelRadius) * Math.cos(angleForLabel));
canvas.drawText(String.valueOf(labelText), (float) label_X_Position, (float) label_Y_Position, paint);
but when the length of the label value increased it doesn't looks like circular path. For better understanding i have attached a diagramatic representation for my requirment,
in the above image, while placing the value 100000 as per existing implementation, the text value will start from the arc. I need that to be shared based on its length.

Custom shaped textview, Android

I'm looking for a way of making custom shaped text input control on Android, like showed on screenshot.
What is need is to have some predefined places(gray rects) that will not be covered by text. So when user typing - text will lays out with some offsets from left side, or right side, depending on place of gray rect.
It is easy to do on iOS, just a couple lines of code and that is all. But I can not find a way of doing this on Android. Note that gray rects may not be a part of text input component. On iOS I simply put two UIImageView over the UITextView, and set up rects for excluding from rendering text:
CGFloat margin = 8;
CGRect firstPathRect = CGRectMake(0, 80, 160 + margin, 90 + margin);
CGRect secondPathRect = CGRectMake([UIScreen mainScreen].bounds.size.width - 160 - 2 * margin, 280, 160, 90 + margin);
UIBezierPath *path1 = [UIBezierPath bezierPathWithRect:firstPathRect];
UIBezierPath *path2 = [UIBezierPath bezierPathWithRect:secondPathRect];
self.textView.textContainer.exclusionPaths = #[path1, path2];
I hope somebody can help me with this task. Thanks in advance!
Check out this library for flowing text:
https://github.com/deano2390/FlowTextView

Android Monospace fonts aren't fixed width

I'm writing a custom View for a timer, but I can't get a properly fixed-width font for the numbers in the middle. Here's the relevant code (from two different methods):
mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setTypeface(Typeface.create(Typeface.MONOSPACE, Typeface.NORMAL));
// Get rectangle that is the size of the text
mTextPaint.getTextBounds(
getTimeText(),
0,
getTimeText().length(),
mTextBounds);
// X and Y coordinates of text
float mTextPosX = (width / 2) - (mTextBounds.width() / 2);
float mTextPosY = (height / 2) + (mTextBounds.height() / 2);
// Draw text
canvas.drawText(
getTimeText(),
mTextPosX,
mTextPosY,
mTextPaint);
This seems to work fine, but whenever one of the numbers changes to a 1, that 1 takes up less space and the whole chunk of text moves over a bit. This obviously should not be happening, since I'm using a monospace font. Can anyone shed some light on how I might fix this?
I had the same problem. A way to fix it is by using an external font in which "1" behaves as expected. A website with many fonts to try is this one: http://www.fontcubes.com/ . A related discussion on how to use external fonts in Android is here: How to change fontFamily of TextView in Android .

Android: Measure Text Height on a Canvas

I am currently working on rendering a Bitmap, that I then want to send to a mobile printer. However, I am struggling with measuring the height of my text, so I can advance the y position appropriately.
My basic bitmap/canvas/paint configuration is this (Font Size is 16 and the dimensions of the bitmap are 200x400 (width x height):
public MyRenderer() {
// Initialize bitmap
bitmap = Bitmap.createBitmap(200, 400, Bitmap.Config.ARGB_8888);
// Initialize canvas
canvas = new Canvas(bitmap);
// Initialize brush (Paint instance)
brush = new Paint();
brush.setTextSize(16);
brush.setTypeface(Typeface.SANS_SERIF);
brush.setColor(Color.BLACK);
brush.setStyle(Paint.Style.FILL);
brush.setAntiAlias(true);
brush.setTextAlign(Align.LEFT);
}
So far so good, now what I want to do is: If I use the Paint's method drawText I need to supply the x and y coordinates. As for x that's zero (assuming left aligned text) but as for y, I'd have to calculate the height of each text I print and add it up, so I can keep track of my current y position.
And this is where it gets odd: I am using the following method to determine the height of a text (using the Paint objected that I initialized previously - it's called "brush"):
public int measureHeight(String text) {
Rect result = new Rect();
// Measure the text rectangle to get the height
brush.getTextBounds(text, 0, text.length(), result);
return result.height();
}
The above method returns the following values for the following texts:
"Hello World" returns a height of 12
"A camera instance can be used to compute 3D transformations and generate a matrix." returns a height of 16
"Introducing Android Design: The place to learn about principles, building blocks, and patterns for creating world-class Android user interfaces. Whether you're a UI professional or a developer playing that role, these docs show you how to make good design decisions, big and small." returns a height of 16
It makes sense to me, that number 2 and 3 return a greater height than number 1 but if one line has a height of 12 (as number one does) - it makes no sense, that multiple lines have a height of 16 ?
Am I missing something here? There is a convenience method for measuring the width of a text (using an instance of paint and call measureText("myText") which works perfectly, however I am quite at a loss, when it comes to the height, as the above given results don't make any sense to me.
EDIT
I am aware, that getTextBounds probably does no auto-wrapping of multi-lined text, and that's ok, I already wrote a method for splitting text, but even if it just measures one line, the above given length values still seem unlikely.
I think it is because the "p" in "compute" extends below the baseline whereas "Hello World" only contains letters that are above the baseline.
Since the line distance should not depend on what specific letters your text happens to consist of you are probably looking for Paint.FontMetrics which can be obtained via Paint.getFontMetrics(). Compute descent - ascent + leading to get the recommended baseline distance (because ascent has a negative value).
There is a small error in the accepted answer. If you want the text height, you should use
Paint.FontMetrics fm = mTextPaint.getFontMetrics();
float textHeight = fm.descent - fm.ascent;
And if you want the line height, you should use
float lineHeight = fm.bottom - fm.top + fm.leading;
Leading is optional interline spacing, so if you need to get the line hight you can include it. But if you just want the text height, then you can leave it off.
Note
I've never actually seen leading be anything else than 0, and as far as I can tell it even seems to be ignored in the TextView source code (and its associated Layout, StaticLayout, etc.). Please correct me if I'm wrong. So it is probably safe it leave it out of line hight calulations, but I'm not completely sure about that.
See also
Getting text height from getTextBounds vs FontMetrics vs StaticLayout
Meaning of top, ascent, baseline, descent, bottom, and leading in Android's FontMetrics

Categories

Resources