Centering Text on Android Canvas Including Accurate Bounds - android

Here is the situation. I am rendering to a canvas. Nothing else is being rendered, the canvas is effectively fullscreen. There is only one view.
I want to make a simple (text) button. This consists of two parts: visually drawing it, and checking bounds to see if it is pressed. These two things should be consistent.
Ideally the centering is optional, though I would expect switching it to be simple.
After far too many hours, I suspect it is not simply an alignment issue, but also a use of the API that is the source of the problem.
How can this be done?

Just use static layouts and then you can keep the text center aligned inside the canvas even if the size is increased by some limit by auto aligning the text.
Rect bounds = new Rect(x1, y1, x2, y2);// set the bounds
String textOnCanvas = "text to be wriiten";
StaticLayout sl = new StaticLayout(textOnCanvas, textPaint,
bounds.width(), Layout.Alignment.ALIGN_CENTER, 1, 1, true);
canvas.save();
float textHeight = getTextHeight(textOnCanvas, textPaint);
int numberOfTextLines = sl.getLineCount();
float textYCoordinate = bounds.exactCenterY() -
((numberOfTextLines * textHeight) / 2);
//text will be drawn from left
float textXCoordinate = bounds.left;
canvas.translate(textXCoordinate, textYCoordinate);
//draws static layout on canvas
sl.draw(canvas);
canvas.restore();
The parameter Layout.Alignment.ALIGN_CENTER of static layout will take care of the center alignment of the text in canvas.

Related

Problems Finding Center of Android Wear Watchface

I have looked all over, but nothing has worked. Every time I try finding the center of the screen for my watch face, it always end up slightly higher than the center, and to the far right of the screen (This is different than the problem that the original template from Android Studio has, where the Watch face is slightly up and all the way to the far left of the screen). What could I be doing wrong?
(First screenshot on Left: My problem. Second screenshot: Original form of the Android Wear template from Android Studio)
#Override
public void onDraw(Canvas canvas, Rect bounds) {
if (isInAmbientMode()) {canvas.drawColor(Color.BLACK);
}
else {
canvas.drawRect(0, 0, bounds.width(), bounds.height(), mBackgroundPaint);
}
// Draw H:MM in ambient mode or H:MM:SS in interactive mode.
long now = System.currentTimeMillis();
mCalendar.setTimeInMillis(now);
int width = bounds.width();
int height = bounds.height();
float centerX = width / 2f;
float centerY = height / 2f;
String text = mAmbient
? String.format("%d:%02d", mCalendar.get(Calendar.HOUR),
mCalendar.get(Calendar.MINUTE))
: String.format("%d:%02d", mCalendar.get(Calendar.HOUR),
mCalendar.get(Calendar.MINUTE));
canvas.drawText(text, centerX, centerY, mTextPaint);
}
Full code can be found here
As far as i can tell from a quick look on your sources you already calculate mTextXOffset and mTextYOffset but don't use it for your draw call, hence the texts origin is centered in your problematic screenshot instead of the texts center.
adjusting your centerX and centerY by mTextXOffset and mTextYOffset should do the trick.
If you're drawing text using a center point you can use
mTextPaint.setTextAlign(Paint.Align.CENTER);
which will draw from the center. Otherwise the x coordinte represents the left hand side of the text you are drawing.

Android - Fill Path with color partially

I am trying to draw a heart shaped Canvas using Path in Android. The code is as follows :
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Fill the canvas with background color
canvas.drawColor(Color.WHITE);
// paint.setShader(null);
// Defining of the heart path starts
path.moveTo(left + WIDTH / 2, top + HEIGHT / 4); // Starting point
// Create a cubic Bezier cubic left path
path.cubicTo(left+WIDTH/5,top,
left+WIDTH/4,top+4*HEIGHT/5,
left+WIDTH/2, top+HEIGHT);
// This is right Bezier cubic path
path.cubicTo(left + 3 * WIDTH / 4, top + 4 * HEIGHT / 5,
left + 4 * WIDTH / 5, top,
left + WIDTH / 2, top + HEIGHT / 4);
paint.setShader(new LinearGradient(0, canvas.getHeight()/4, canvas.getWidth(), canvas.getHeight()/4, new int[]{Color.RED, Color.YELLOW, Color.GREEN}, new float[]{0, 0.6f, 1}, Shader.TileMode.CLAMP));
canvas.drawPath(path, paint);
heart_outline_paint.setColor(getResources().getColor(R.color.heart_outline_color)); // Change the boundary color
heart_outline_paint.setStrokeWidth(4);
heart_outline_paint.setStyle(Paint.Style.STROKE);
canvas.drawPath(path, heart_outline_paint);
}
I am able to draw heart without any issue and I am able to fill color inside the heart using the Fill option in Paint. But I should be able to fill the heart dynamically according to some data and it cannot be filled fully all the time. What I have achieved so far is as follows :
I have made an extensive search and came across a lot of things similar to this. Some of which includes :
Android fill in part of a path?
filling a circle gradually from bottom to top android
I also came across the concept of converting the canvas to bitmap and filling color inside the bitmap using Flood Fill Algorithm which lets users to fill colors inside the bitmap. However, I do not want the bitmap to fill the color while touching inside the heart but to fill while a button click action.
I thought that filling a circle gradually from bottom to top android
would give help me but it makes use of a circle and I am not well-versed in Canvas which makes me very weak in adapting the circle code to such a shape.
If anybody has some ideas or any insights on how to achieve this, it will be really helpful. Cheers. Thanks in advance.
P.S : I also tried some tricks using setShader in Paint but nothing would give me what I want.
EDIT :
I just stumbled upon a idea of drawing a rectangle over the heart with another color same as the background of the canvas so that it will look like its half filled !! I am still working on the idea and not sure how accurate this is gonna be for me. If someone has a better idea, you're most welcome.
I used clipPath function available in Canvas to achieve what I needed. I draw the heart by above method and draw a rectangle over it, and I use the clipPathfunction to clip out the region that is outside the heart.
public static double filled_amount = .90;
path.moveTo(left_x_moveto, left_y_moveto);
path.cubicTo(left_x1, left_y1, left_x2, left_y2, left_x3, left_y3);
path.cubicTo(right_x2, right_y2, right_x1, right_y1, left_x_moveto, left_y_moveto);
path.close();
Rect rect = new Rect((int)(canvas.getWidth()*.10),(int)(canvas.getHeight()*filled_amount),(int) canvas.getWidth(), (int) canvas.getHeight());
canvas.clipPath(path);
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.FILL);
canvas.drawPath(path, paint);
canvas.drawRect(rect, rect_paint);
heart_outline_paint.setColor(getResources().getColor(R.color.heart_outline_color)); // Change the boundary color
heart_outline_paint.setStrokeWidth(15);
heart_outline_paint.setStyle(Paint.Style.STROKE);
canvas.drawPath(path, heart_outline_paint);
This will give me the desired result of filling the heart dynamically. Changing the value of filled_amount dynamically and calling invalidate() will make it look like the heart is being filled dynamically.
#Henry's answer might be a better one but this did the trick for me and I dont look deeply in to the edges so a bit of zig-zags here and there is all right.
You could use Bitmap Masking to get a partially filled Heart. What you ideally do here is use one bitmap to mask the other.
In your case you could have a filled rectangle in the canvas and you have then have the heart shape in a new bitmap to act as the mask. You could then dynamically change the filling of the heart by changing the height of the background rectangle.
Refer this: https://stackoverflow.com/a/33483600/4747587. This contains the implementation of partially filling a Star. The idea is the same.

Beautiful Stroked Text in Android

I know how to stroke text using custom views(EditText or TextView) but I couldn't able to achieve something beautiful like this one, which is done using Photoshop. And yes, it has outer shadow too.
What I have done so far is adjusting stroke width and stroke join style. However, if I increase the stroke width, the stroke took place the whole text.
As far as I have searched, there is a library called MagicTextView but it also couldn't give the result like that above.
Update: I have tweaked things based on suggestion by #pskink. It works now. However I can't drag anymore. If I drag that EditText, there is some weird lines showed up like this.
Here is the code:
#Override public void onDraw(Canvas canvas) {
final int x = this.getLeft();
final int y = this.getBottom();
mText = this.getText().toString();
p.setStrokeWidth(30);
p.setStyle(Style.STROKE);
p.setStrokeJoin(Join.ROUND);
p.setColor(0xffffffff);
canvas.drawText(mText, x, y, p);
p.setStyle(Style.FILL);
p.setColor(0xff000000);
canvas.drawText(mText, x, y, p);
}
After a few hours of tweaking, I have fixed the weird line issue stated in updated question. I think I should post here as the answer.
#Override public void onDraw(Canvas canvas) {
mText = this.getText().toString();
p.setStyle(Style.STROKE);
p.setStrokeJoin(Join.ROUND);
p.setShadowLayer(10, 1, 1, 0xcfcccccc);
canvas.drawText(mText, 0, getLineHeight(), p);
p.clearShadowLayer();
p.setStrokeWidth(35);
p.setStrokeJoin(Join.ROUND);
p.setStyle(Style.STROKE);
p.setColor(0xffffffff);
canvas.drawText(mText, 0, getLineHeight(), p);
p.setStyle(Style.FILL);
p.setColor(0xff000000);
canvas.drawText(mText, 0, getLineHeight(), p);
canvas.translate(xPos, yPos);
}
xPos and yPos are x and y values from ACTION_MOVE onTouch event. And We need to add the line height as a Y for the canvas text.
However, if I increase the stroke width, the stroke took place the whole text.
If the problem is that the stroke is centered, and you want it outside the text, see: Android Paint stroke width positioning
You must precalculate the desired width and height according to your stroke.
I would suggest trying a very bold font. In the image below, the white stroke would be centered on the red line (which would be the outline of each black letter).
In response to your update:
If I drag that EditText, there is some weird lines showed up.
The line might be the result of focus on the EditText. You can remove the focus explicitly: Android: Force EditText to remove focus?
Alternatively, you can style the focus (perhaps to be invisible, if it doesn't adversely affect the rest of your UI): How to change the color of a focused EditText when using "android:Theme.Holo.Light"?

StaticLayout height measurements off

I am trying to draw multiline text to a bitmap with the font Latto-Reg, and StaticLayout seems to have problems with it.
paint.setTextSize(label.fontSize);
paint.setTypeface(face);
StaticLayout textLayout = new StaticLayout(label.text, paint, (int)StaticLayout.getDesiredWidth(label.text, paint), Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
Bitmap bitmapAux = Bitmap.createBitmap(textLayout.getEllipsizedWidth(), textLayout.getHeight(), Bitmap.Config.ALPHA_8);
canvas.setBitmap(bitmapAux);
canvas.save();
canvas.translate(0, textLayout.height());
textLayout.draw(canvas);
canvas.restore();
The texture has padding on top and bottom depending on the font and size, while the text fits perfectly in the bitmap it is a lot of wasted memory space and makes laying it out to be off by a random amount.
I tested using single-line drawing and the bitmap was perfectly fitting the text
paint.getTextBounds(label.text, 0, label.text.length(), rect);
Bitmap bitmapAux = Bitmap.createBitmap(rect.width(), rect.height(), Bitmap.Config.ALPHA_8);
canvas.drawText(label.text, -rect.left, -rect.bottom, paint);
I have tried getting all kinds of metrics from StaticLayout and all of them seem to be off from the text: line 0 bounds, line 0 top, last line bottom...leading to the same padding problems.
EDIT:
I solved the problem by using offset-based single line drawing. Still the StaticLayout class was drawing incorrectly with several different non-standard fonts and I want to know why.
Looking at the android developper page, it looks like it's designed to handle both the multi-line case and being used next to another Layout well, and hence there is space on top of the line of text so that if you place it directly below another Layout it will be correctly spaced. In essence, it's just not designed for what you are trying to achieve.
Overall, it may be easier to get the Text bounds from Paint.getTextBounds() to know what the extent of the text will be within the Layout.
I've created a minimal working example of what I think you're trying to accomplish: creating a bitmap precisely large enough to contain the text rendered through a StaticLayout.
It seems that there are a few things wrong with your code:
You're needlessly translating vertically inside the bitmap;
There doesn't appear to be a height() method for StaticLayout.
Here's my result:
I added a green background to illustrate the size of the bitmap, but otherwise, my code differs very little from yours:
public void createTexture() {
int width = textLayout.getEllipsizedWidth();
int height = textLayout.getHeight();
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas2 = new Canvas(bitmap);
Paint p2 = new Paint();
p2.setStyle(Style.FILL);
p2.setColor(Color.GREEN);
canvas2.drawRect(0, 0, width, height, p2);
textLayout.draw(canvas2);
}
I created a very simple custom component to draw the bitmap:
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(bitmap, 0, 0, paint);
}
It seems that perhaps you're translating to draw multiple textures after one another. I'd recommend that you do so in your draw method instead, translating vertically in the height of the previous texture after drawing it.

Get number of lines StaticLayout will split text into

I'm drawing some text onto a canvas, and am using a StaticLayout to wrap the text across the screen. I want to align the text so that the bottom of the text is on the bottom of the screen.
To do this I need to know how many lines the StaticLayout has wrapped the text into, so i can multiply that by the font size, and offset it that much from the height of my component.
This is my StaticLayout:
main = new TextPaint();
main.setTextSize(textSize);
main.setColor(Color.WHITE);
bottomText = new StaticLayout("Long text that requires wrapping.", main, getWidth(), Layout.Alignment.ALIGN_CENTER, 1f, 1.0f, false);
And I'm moving it down by translating my canvas:
canvas.translate(0, getHeight() / 2);
bottomText.draw(canvas);
canvas.restore();
So; how do I align it to the bottom, or get the number of lines it has been split into?
How about StaticLayout.getLineCount()?

Categories

Resources