The following question might sound a bit stupid but I guess stupidity has no limit so here it goes. I am drawing a Heart using Canvas in Android and have got no issues in drawing the heart but I am not able to make the heart sharp in the meeting point. My heart looks like
CODE :
left_x_moveto = 200;
left_y_moveto = 45;
left_x1 = 197;
left_y1 = -35;
left_x2 = 60;
left_y2 = 20;
left_x3 = 193;
left_y3 = 130;
right_x_moveto = 200;
right_y_moveto = 45;
right_x1 = 197;
right_y1 = -35;
right_x2 = 345;
right_y2 = 20;
right_x3 = 193;
right_y3 = 130;
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);
path.moveTo(left_x_moveto, left_y_moveto);
path.cubicTo(left_x1, left_y1, left_x2, left_y2, left_x3, left_y3);
path.moveTo(right_x_moveto, right_y_moveto);
path.cubicTo(right_x1, right_y1, right_x2, right_y2, right_x3, right_y3);
canvas.drawPath(path, heart_outline_paint);
What have I tried so far :
Reducing or increasing the points of left_x_moveto,left_y_moveto and vice versa but the heart is completely disfigured for which I am unable to find the reason.
When the right_x_moveto = 198 and right_y_moveto = 45, the heart looks like
I am not sure why this is happening.
Reducing the width of the heart_outline_paint would give me what I want but I want the thickness of the heart to be the same so reducing the setStrokeWidth is not an option.
In short, I want both the curves to MEET AND MERGE and not just MEET.
Any help will be much appreciated. Thanks in advance.
You need to perform a couple of this.
Close the path via path.close().
You need to set the stroke join via heart_outline_paint.setStrokeJoin(Paint.Join.MITER);
A path can be closed only if it's drawn continuously. Hence I have modified the code so that path.close() can be done properly. Below is the code.
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
heart_outline_paint = new Paint(Paint.ANTI_ALIAS_FLAG);
heart_outline_paint.setStrokeJoin(Paint.Join.MITER);
path = new Path();
int left_x_moveto = 200;
int left_y_moveto = 45;
int left_x1 = 180;
int left_y1 = -20;
int left_x2 = 30;
int left_y2 = 20;
int left_x3 = 193;
int left_y3 = 130;
int right_x_moveto = 200;
int right_y_moveto = 45;
int right_x1 = 214;
int right_y1 = -20;
int right_x2 = 375;
int right_y2 = 20;
int right_x3 = 193;
int right_y3 = 130;
heart_outline_paint.setColor(Color.RED); // Change the boundary color
heart_outline_paint.setStrokeWidth(15);
heart_outline_paint.setStyle(Paint.Style.STROKE);
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, right_x_moveto, right_y_moveto);
path.close();
canvas.drawPath(path, heart_outline_paint);
}
Paint.Join.MITER is the one that does what you want.
The outer edges of a join meet at a sharp angle
Now this MITER join works only when the angle is <= 90 degree. But here, based on the values that you have provided, the angle is 90 degree and hence the MITER join doesn't work. I have modified the values to get the following image. The image is not exact, but you need to play around with the value the get the right one.
You could set the ROUND join method to get the following.
The problem is with Path.cubicTo(). It's very hard to get the MITTER join work without getting the heart shape squished. So instead of cubicTo, I tried with lineTo and arcTo to create a simple heart. The below is the code for that. You will notice that I have rotated the canvas to 45 degrees and then drew the heart shape. This is purely for convenience, so that the coordinates are simple and does not involve pythagoras theorem.
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
heart_outline_paint = new Paint(Paint.ANTI_ALIAS_FLAG);
heart_outline_paint.setStrokeJoin(Paint.Join.MITER);
heart_outline_paint.setColor(Color.RED); // Change the boundary color
heart_outline_paint.setStrokeWidth(15);
heart_outline_paint.setStyle(Paint.Style.STROKE);
path = new Path();
float length = 100;
float x = canvas.getWidth()/2;
float y = canvas.getHeight()/2;
canvas.rotate(45,x,y);
path.moveTo(x,y);
path.lineTo(x-length, y);
path.arcTo(new RectF(x-length-(length/2),y-length,x-(length/2),y),90,180);
path.arcTo(new RectF(x-length,y-length-(length/2),x,y-(length/2)),180,180);
path.lineTo(x,y);
path.close();
canvas.drawPath(path, heart_outline_paint);
}
The final rendered image of this code is below:
What's happening is that the thickness of the line is being drawn on one side, not on both. In true MS paint fashion:
(The left is what's happening, the right is what you want. Black is the actual position of the line would width be 1px, red is the "padding", the 2nd, 3rd through 15th pixel, heart_outline_paint.setStrokeWidth(15);)
To fix this, try to subtract half the width of the line from the x of right line, and add it to the x of the left line. Doing so will work around this problem, not fix it
Related
I'm drawing 3 things in my custom view in the onDraw() method: a vector drawable, a simple line and a triangle (made from 4 Points and a Path). This custom view is displayed in a tab.
If I swipe to go to another tab I see that the system calls onDraw(). When I return to the tab holding my custom view the vector drawable and simple line are still visible but the triangle has disappeared. If I now swipe to another tab, onDraw() runs again and back in the tab with the custom view, all items (including the triangle) are now visible. This disappearing/appearing continues to happen as I swipe back and forth. Why is my triangle disappearing?
UPDATE 1 (hacky fix):
I've tried experimenting and notice that when I move my triangle Path object creation out of my init() method and put it directly in the onDraw() method - then all works well, nothing disappears. But, I now get the 'Avoid object allocations during draw' warning as I'm creating this object in onDraw();
UPDATE 2 (better fix?):
After more experimenting, it's definitely the Path causing this problem. Another solution to this - which doesn't incur the 'Avoid object allocations during draw' warning is: keep Path creation in init() and remove the line of code 'myPath.setFillType(Path.FillType.EVEN_ODD)'. It solves my problem, but I've no idea why.
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Co-ordinates
int width = getWidth();
int halfWidth = width/2;
int left = 0;
int top = 0;
int centreX = left + halfWidth;
int centreY = top + halfWidth;
int baseSize = Math.round((float)(halfWidth * 0.05));
// Vector drawable - always draws fine!
myVectorDrawable.setBounds(left, top, left + width, top + width);
myVectorDrawable.draw(canvas);
// Simple line - always draws fine!
canvas.drawLine(left, top, 20, 20, paint);
// Triangle - sometimes visible, sometimes disappears!
Point myTriangleBottomMiddle = new Point(centreX, centreY);
Point myTriangleBottomLeft = new Point(centreX, centreY + baseSize);
Point myTriangleBottomRight = new Point(centreX, centreY - baseSize);
Point myTriangleTopMiddle = new Point(centreX + halfWidth, centreY);
myPath.moveTo(myTriangleBottomMiddle.x, myTriangleBottomMiddle.y);
myPath.lineTo(myTriangleBottomLeft.x, mTriangleBottomLeft.y);
myPath.lineTo(myTriangleTopMiddle.x, myTriangleTopMiddle.y);
myPath.lineTo(myTriangleBottomRight.x, myTriangleBottomRight.y);
mPath.close();
canvas.drawPath(myPath, myPaint);
}
Below the code where I set up stuff so as not to burden the onDraw() method.
private void init() {
// Vector drawable
myVectorDrawable = r.getDrawable(R.drawable.gauge_dial);
// Triangle path - ** THIS BEING HERE SEEMS TO BE THE PROBLEM **
myPath = new Path();
myPath.setFillType(Path.FillType.EVEN_ODD);
// Triangle Paint
myPaint = new Paint();
myPaint.setColor(r.getColor(R.color.black));
myPaint.setStrokeWidth(2);
myPaint.setAntiAlias(true);
myPaint.setStyle(Paint.Style.FILL);
// Simple line paint
Paint paint = new Paint();
paint.setColor(Color.BLACK);
}
Add a call to reset() on the path.
// Triangle - sometimes visible, sometimes disappears!
Point myTriangleBottomMiddle = new Point(centreX, centreY);
Point myTriangleBottomLeft = new Point(centreX, centreY + baseSize);
Point myTriangleBottomRight = new Point(centreX, centreY - baseSize);
Point myTriangleTopMiddle = new Point(centreX + halfWidth, centreY);
myPath.reset();
myPath.moveTo(myTriangleBottomMiddle.x, myTriangleBottomMiddle.y);
myPath.lineTo(myTriangleBottomLeft.x, mTriangleBottomLeft.y);
myPath.lineTo(myTriangleTopMiddle.x, myTriangleTopMiddle.y);
myPath.lineTo(myTriangleBottomRight.x, myTriangleBottomRight.y);
mPath.close();
canvas.drawPath(myPath, myPaint);
Also, I would recommend putting the vector drawable into a separate view so it's not redrawn everytime you need to animate the triangle (assuming this is going to be an animated guage dial).
I have two bitmaps that I draw onto the center of a canvas:
One is only a background, it's a spirit level in top view which doesnt move. The second one is a bitmap looking like a "air bubble". When the user tilts the phone, the sensors read the tilt and the air bubble moves according to the sensor values along the x-axis. However, I need to make sure that the air bubble doesnt move too far, e.g out of the background-bitmap.
So I tried to which x coordinate the bubble can travel to,
before I have to set xPos = xPos -1 using trial and error
This works fine on my device.
To clarify: On my phone, the air bubble could move to the coordinate x = 50 from the middle of the screen. This would be the point, where the bitmap is at the very left of the background spirit level.
On a larger phone, the position x = 50 is too far left, and therefore looking like the air bubble travelled out of the water level.
Now I've tried following:
I calculated the area in % in which the air bubble can move. Let's say that
is 70% of the entire width of the bitmap. So I tried to calculate the two x boundary values:
leftBoundary = XmiddlePoint - (backgroundBitmap.getWidth() * 0.35);
rightBoundary = XmiddlePoint + (backgroundBitmap.getWidth() * 0.35);
...which doesnt work when testing with different screen sizes :(
Is it possible to compensate for different screen sizes and densities using absolute coordinates or do I have to rethink my idea?
If you need any information that I forgot about, please let me know. If this question has already been answered, I would appreciate a link :) Thanks in advance!
Edit:
I load my bitmaps like this:
private Bitmap backgroundBitmap;
private static final int BITMAP_WIDTH = 1898;
private static final int BITMAP_HEIGHT = 438;
public class SimulationView extends View implements SensorEventListener{
public SimulationView(Context context){
Bitmap map = BitmapFactory.decodeResource(getResources, R.mipmap.backgroundImage);
backgroundBitmap = Bitmap.createScaledBitmap(map, BITMAP_WIDTH, BITMAP_HEIGHT, true;
}
and draw it like this:
protected void onDraw(Canvas canvas){
canvas.drawBitmap(backgroundBitmap, XmiddlePoint - BITMAP_WIDTH / 2, YmiddlePont - BITMAP_HEIGHT / 2, null);
}
backgroundBitmap.getWidth() and getHeight() prints out the correct sizes.
Calculating like mentioned above would return following boundaries:
DisplayMetrics displayMetrics = new DisplayMetrics();
((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int width = displayMetrics.widthPixels;
//which prints out width = 2392
xMiddlePoint = width / 2;
// = 1196
leftBoundary = xMiddlePoint - (BITMAP.getWidth()* 0.35);
// = 531,7
However, when I use trial and error, the right x coordinate seems to be at around 700.
I've come across a great explanation on how to fix my issue here.
As user AgentKnopf explained, you have to scale coordinates or bitmaps like this:
X = (targetScreenWidth / defaultScreenWidth) * defaultXCoordinate
Y = (targetScreenHeight / defaultScreenHeight) * defaultYCoordinate
which, in my case, translates to:
int defaultScreenWidth = 1920;
int defaultXCoordinate = 333;
DisplayMetrics displayMetrics = new DisplayMetrics();
((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
displayWidth = displayMetrics.widthPixels;
leftBoundary = (displayWidth / defaultScreenWidth) * defaultXCoordinates
I need to draw something like this:
I was hoping that this guy posted some code of how he drew his segmented circle to begin with, but alas he didn't.
I also need to know which segment is where after interaction with the wheel - for instance if the wheel is rotated, I need to know where the original segments are after the rotation action.
Two questions:
Do I draw this segmented circle (with varying colours and content placed on the segment) with OpenGL or using Android Canvas?
Using either of the options, how do I register which segment is where?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
EDIT:
Ok, so I've figured out how to draw the segmented circle using Canvas (I'll post the code as an answer). And I'm sure I'll figure out how to rotate the circle soon. But I'm still unsure how I'll recognize a separate segment of the drawn wheel after the rotation action.
Because, what I'm thinking of doing is drawing the segmented circle with these wedges, and the sort of handling the entire Canvas as an ImageView when I want to rotate it as if it's spinning. But when the spinning stops, how do I differentiate between the original segments drawn on the Canvas?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I've read about how to draw a segment on its own (here also), OpenGL, Canvas and even drawing shapes and layering them, but I've yet to see someone explaining how to recognize the separate segments.
Can drawBitmap() or createBitmap() perhaps be used?
If I go with OpenGL, I'll probably be able to rotate the segmented wheel using OpenGL's rotation, right?
I've also read that OpenGL might be too powerful for what I'd like to do, so should I rather consider "the graphic components of a game library built on top of OpenGL"?
This kind of answers my first question above - how to draw the segmented circle using Android Canvas:
Using the code found here, I do this in the onDraw function:
// Starting values
private int startAngle = 0;
private int numberOfSegments = 11;
private int sweepAngle = 360 / numberOfSegments;
#Override
protected void onDraw(Canvas canvas) {
setUpPaint();
setUpDrawingArea();
colours = getColours();
Log.d(TAG, "Draw the segmented circle");
for (int i = 0; i < numberOfSegments; i++) {
// pick a colour that is not the previous colour
paint.setColor(colours.get(pickRandomColour()));
// Draw arc
canvas.drawArc(rectF, startAngle, sweepAngle, true, paint);
// Set variable values
startAngle -= sweepAngle;
}
}
This is how I set up the drawing area based on the device's screen size:
private void setUpDrawingArea() {
Log.d(TAG, "Set up drawing area.");
// First get the screen dimensions
Point size = new Point();
Display display = DrawArcActivity.this.getWindowManager().getDefaultDisplay();
display.getSize(size);
int width = size.x;
int height = size.y;
Log.d(TAG, "Screen size = "+width+" x "+height);
// Set up the padding
int paddingLeft = (int) DrawArcActivity.this.getResources().getDimension(R.dimen.padding_large);
int paddingTop = (int) DrawArcActivity.this.getResources().getDimension(R.dimen.padding_large);
int paddingRight = (int) DrawArcActivity.this.getResources().getDimension(R.dimen.padding_large);
int paddingBottom = (int) DrawArcActivity.this.getResources().getDimension(R.dimen.padding_large);
// Then get the left, top, right and bottom Xs and Ys for the rectangle we're going to draw in
int left = 0 + paddingLeft;
int top = 0 + paddingTop;
int right = width - paddingRight;
int bottom = width - paddingBottom;
Log.d(TAG, "Rectangle placement -> left = "+left+", top = "+top+", right = "+right+", bottom = "+bottom);
rectF = new RectF(left, top, right, bottom);
}
That (and the other functions which are pretty straight forward, so I'm not going to paste the code here) draws this:
The segments are different colours with every run.
I'm painting text over a background image on a canvas. I move the image interactively (like a Ouija board pointer). I've set the canvas to black, the pointer is red and I want to write white text over it so that the pointer has a player's name on it.
In Android 2.3.4 it appears as solid white text on top of the red pointer which is pretty clear, but I'd like to use any color. In Android 4.1.2 I can barely see the white text. Here's my code:
public Pointer(Context context) {
super(context);
paintBg = new Paint();
paintBg.setColor(Color.BLACK);
paintName = new Paint();
paintName.setColor(Color.WHITE);
paintName.setTextSize(50); // set text size
paintName.setStrokeWidth(5);
paintName.setTextAlign(Paint.Align.CENTER);
this.setImageResource(res); // pointer.png in res/drawable folder
Drawable d = getResources().getDrawable(res);
h = d.getIntrinsicHeight();
w = d.getIntrinsicWidth();
canvas = new Canvas();
canvas.drawPaint(paintBg);//make background black
// float imageScale = width / w; // how image size scales with screen
}
#Override
public void onDraw(Canvas canvas) {
y = this.getHeight() / 2; // center of screen
x = this.getWidth() / 2;
int left = Math.round(x - 0.8f * w);
int right = Math.round(x + 0.8f * w);
canvas.save();
canvas.rotate((direction + 180) % 360, x, y); // rotate to normal
canvas.drawText(s, x, y + 20, paintName); // draw name
canvas.restore();
canvas.rotate(direction, x, y); // rotate back
super.onDraw(canvas);
}
What changed in 4.1.2 that would affect this, or am I doning something incorrectly? Thanks for your help with this as it's driving me crazy.
Edit to include screen shots:
Android 2.3.4
Android 4.1.2
Note how the white text appears to be on top in 2.3.4 while it appears below or muddy in 4.1.2.
As free3dom pointes out it is related to alpha. I do change alpha because if I don't, the text does not appear on top of the arrow. It appears that the ImageView having the pointer image is always on top - could this be what's going on?
Here is how I handle setting alpha:
public static void setAlpha(View view, float alpha, int duration) {
if (Build.VERSION.SDK_INT < 11) {
final AlphaAnimation animation = new AlphaAnimation(alpha, alpha);
animation.setDuration(duration);
animation.setFillAfter(true);
view.startAnimation(animation);
} else //for 11 and above
view.setAlpha(alpha);
}
Maybe it has something to do with using this.setImageResource(res) to set the image resource? According to android developer guide, I can only set alpha to the single view and everything in the view is changed. Yet if I lower the alpha, the arrow image seems to become transparent enough to allow me to see the text.
You set a stroke width, but never indicate that stroke should be used for the Paint.
Try adding
paintName.setStyle( FILL_AND_STROKE );
I am trying to draw a cupola circles at random positions in an Android application.
I draw them on a bitmap and then draw that bitmap on the canvas. This is the function where a draw the circles:
private void drawRandomCircles(int numOfCircles) {
Canvas c = new Canvas(b);
Paint cPaint = new Paint;
cPaitn.setColor(Color.RED);
for(int i = 0; i < numOfCircles; i++) {
int x = Math.Random % 100;
int y = Math.Random % 100;
c.drawCircle(x, y, 20, cPaint)
}
}
The Bitmap b is global.
And after calling this function I just draw the bitmap in the onDraw method.
Now the problem is that I only get one circle drawn on the screen, no matter the size of numOfCircles.
Any clue what is happening here?
That code doesn't even compile. What is new Paint; for instance?
I suggest you log your arguments to drawCircle to make sure you draw them on different locations. If Math.Random for instance is a field, it would change in between reads, which would put the circles on top of each other.
If you intended to write Math.random() the error is that Math.random() returns a value between 0 and 1. You may want to use
Random r = new Random();
// your loop
int x = r.nextInt(100);
int y = r.nextInt(100);