I've drawn 5 bitmaps from .png files on a canvas - a head, a body and two arms and legs.
How can I detect which of these has been touched on an OnTouch? And, more specifically, can I detect if the OnTouch was within the actual shape of the body part touched?
What I mean is, obviously, the .pngs themselves are rectangular, but does Android know, or can I tell it, to ignore the transparency within the image?
You could get the colour of pixel touched and compare it to the colour of pixel on the background at those co-ords.
EDIT: ok, ignore that, you can't get the colour of a pixel on the canvas, so instead, get the x,y of the touch, check if any of the body part images have been touched, if so, take the x,y of the image from the touch x,y, then get the pixel of the image, which should be transparent or colour.
public boolean onTouchEvent(MotionEvent event)
{
int x = (int) event.getX();
int y = (int) event.getY();
int offsetx, offsety;
for(int i = 0;i<NUM_OF_BODY_PARTS;i++)
{
if(bodyPartRect[i].intersects(x,y,x+1,y+1))
{
offsetx = x - bodyPartRect[i].left;
offsety = y - bodyPartRect[i].top;
if(bodyPartBMP[i].getPixel(offsetx,offsety) == TRANSPARENT)
{
//whatever
}
}
}
}
Related
I am developing a Gdx game but I have got stuck on some part and I will explain briefly about it:
I have 9 balls organized on 3*3 and I need to detect the ball that I'm touching as shown in the image below:
enter image description here
and I typed this code:
for (int i : listBalls) {
touchPoint = new Vector3(Gdx.input.getX(), Gdx.input.getY(), 0);
rectangle = new Rectangle(sprite[i].getX(), sprite[i].getY(), spriteSize, spriteSize);
if (rectangle.contains(touchPoint.x, touchPoint.y)) {
Gdx.app.log("Test", "Touched dragged " + String.valueOf(i));
pointer = i;
}
}
In case of touching any ball of the above row, it detects the opposite ball in the bottom row. For example in the above image, if I touch ball no 2 on the top it will point to ball no 8, and same if touching any of the bottom balls.
In case of touching any ball of the middle ball, it gives the right on.
I hope I could explain clearly my issue. Please help.
As explained here: LibGDX input y-axis flipped the coordinate system of the input is inverted on the y-axis, try substracting the screen height of your device or camera
int y = screenHeight - Gdx.input.getY();
Keep in mind that using a Camera and unprojecting the input coordinates is the most recommended way to go about detecting input in LibGDX. Example:
#Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
Vector3 unprojected = camera.unproject(new Vector3(screenX, screenY,0));
}
You don't even have to invert the y-coordinate, unproject() does this automatically for you. To use the correct x and y coordinates you then could use:
float x = unprojected.x;
float y = unprojected.y;
I currently have the following code:
private void drawGreen(Canvas canvas) {
greenPaint.setColor(0xFF00AA00);
if (start) {
greenPath = new Path();
greenPath.reset();
greenPath.moveTo(pathArrayX.get(0), pathArrayY.get(0));
start = false;
}
if (isInsideCircle(pathArrayX.get(pathIndex), pathArrayY.get(pathIndex), curX, curY, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 25, getResources().getDisplayMetrics()))) {
greenPath.lineTo(pathArrayX.get(pathIndex), pathArrayY.get(pathIndex));
canvas.drawPath(greenPath, greenPaint);
pathIndex++;
}
}
private boolean isInsideCircle(float x, float y, float centerX, float centerY, float radius) {
return Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2) < Math.pow(radius, 2);
}
In my app, I at first draw a red path, with its coordinates stored in the ArrayLists pathArrayX and pathArrayY. I am tracking the X and Y coordinates of a circular ImageView being moved underneath a mouse, and would like to overlay the red path with a green path when the user hovers over the path from beginning to end. As the user hovers over the red path, the portion of the red path that they already completed would be overlaid by a green path along the same segment. The X and Y coordinates of the ImageView (curX and curY) are being calculated from a running thread.
However, my app doesn't appear to be drawing the green path at all. Is there anything I am doing wrong here?
Is this function even being called?
Assuming it's being called inside onDraw(Canvas), it looks like it might be missing the outer code for a loop. Seeing that you're doing pathIndex++ at the end, were you using a while loop? If you're just going to loop through point, use a for-loop as while-loop is more prone to dropping into an endless loop if you forgot to increment counter or doing wrongly, or do it in multiple places.
Side notes: if the boolean start flag is only being used to lazily initialise greenPath, you should scrap that and just use if (greenPath == null){ as a general practise. Use states that you can directly infer from objects and not use flags if you can help it, this makes code cleaner.
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I need to create a series of concentric ellipses (rings), and need to place user icons on the circumference of these ellipses. See the image below.
Till now I've drawn 3 elliptical concentric circles on canvas & placed user icons.
I need user icons to be draggable across the rings.
Please suggest ways to implement this.
Since it appears you're already placing icons on the circumference of the rings, I assume you know how to do the math [but see edit] to determine the points along the circumference and are asking about the drag-and-drop.
You'll probably want to implement the icon movement using a drag-and-drop approach. Assuming that you keep the rings as a single image, then you will only have a single drop destination. You will then need to analyze the drop point mathematically [see edit] (by ascertaining its pixel color) to determine which ring the icon was dropped into. If you create separate views for the rings, then each one can be its own drop point. (You'll probably need to eventually work out how to redistribute the icons within each ring, but that's a different question.)
Here's some code that shows a minimal way to handle the drag-and-drop using a single image icon on a single view group (where you'd display your rings image).
MainActivity.java
package com.example.dragexample;
import android.app.Activity;
import android.content.ClipData;
import android.os.Bundle;
import android.util.Log;
import android.view.DragEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.DragShadowBuilder;
import android.view.View.OnDragListener;
import android.view.View.OnTouchListener;
import android.widget.ImageView;
public class MainActivity extends Activity {
static final String TAG = "DragActivity";
ImageView icon = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.rings).setOnDragListener(new OnDragListener() {
#Override
public boolean onDrag(View vw, DragEvent event) {
if (event.getAction() == DragEvent.ACTION_DROP) {
// Drop the icon and redisplay it:
icon.setX(event.getX());
icon.setY(event.getY());
icon.setVisibility(View.VISIBLE);
// Analyze the drop point mathematically (or perhaps get its pixel color)
// to determine which ring the icon has been dragged into and then take
// appropriate action.
int destRing = determineDestinationRing(event.getX(), event.getY());
}
return true;
}
});
icon = (ImageView) findViewById(R.id.icon);
icon.setOnTouchListener(new OnTouchListener() {
public boolean onTouch(View vw, MotionEvent event) {
Log.v(TAG, "Touch event " + event.getAction());
if (event.getActionMasked() == MotionEvent.ACTION_MOVE) {
Log.v(TAG, "Starting drag");
// Set up clip data (empty) and drag shadow objects and start dragging:
ClipData cd = ClipData.newPlainText("", "");
DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(vw);
vw.startDrag(cd, shadowBuilder, vw, 0);
vw.setVisibility(View.INVISIBLE);
}
return true;
}
});
}
public void resetImage(View vw) {
Log.v(TAG, "Resetting image position");
icon.setX(0f);
icon.setY(0f);
icon.setVisibility(View.VISIBLE);
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/rings"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:onClick="resetImage" >
<ImageView
android:id="#+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_launcher" />
</FrameLayout>
Edit 1:
To mathematically determine which ring the icon is dropped into, you could use something like the following, which loops through an implementation of the standard equation for an ellipse using hard-coded sizes for the axes. Note that a zero will be returned if an icon is dropped within the innermost 'me' ring. Also, this approach will be a bit more challenging in practice due to the fact that the on-screen ring sizes will likely be adjusted when the layout is rendered. In that case, the final sizes of the axes will need to be determined at run time.
// Axis values must be ascending order; ring 0 is 'me';
float a[] = {50, 100, 150, 200};
float b[] = {100, 200, 300, 400};
public int determineDestinationRing(float x, float y) {
// Check for inclusion within each ring:
for (int i = 0; i < a.length; i++) {
if (((x * x) / (a[i] * a[i]) + (y * y) / (b[i] * b[i])) <= 1)
return i;
}
return -1;
}
To solve the problem, you need to use the equation of ellipse:
(x/a)2 + (y/a)2 = 1 ,
where:
x,y are coordinates of any point on ellipse circumference
a,b are radius on x and y axis respectively.
When the user drags and drops the icon, if the center co-ordinates of the icon intersect with the ellipse circumference, then above formula should hold good. In that case you can place the icon on the ellipse. Since you have 3 ellipses, you will have to do this check couple of times, once each for the other 2 ellipses.
public class CustomView extends View {
// Other methods
public boolean onTouchEvent(MotionEvent e) {
int index = e.getActionIndex();
float x = e.getX(index);
float y = e.getY(index);
int a = this.getWidth()/2;
int b = this.getHeight()/2;
// x-a instead of x and y-b instead of y, to get the offset from centre of ellipse.
double result = Math.pow(((x-a)/a), 2) + Math.pow(((y-b)/b), 2);
Log.v(TAG, "(" + (x-a) + "/" + a + ")2 + (" + (y-b) + "/" + b + ")2 = " + result);
return true;
}
}
I would do it like this. First of all we need to detect which icon is touched on TOUCH_DOWN event. This can be simply done by comparing touch point and icons coordinates. Once the closest icon is found, we should also know which ellipse this icon belongs to, meaning we know both the horizontal and the vertical radiuses of this ellipse. We also know the coordinates of the center of the ellipses.
Then, here is the idea of what we are going to do. We will calculate an angle between the line drawn through the touch point and the center of the ellipses and the horizontal line drawn through the center.
o <- touch point
/
/ \ <- angle
center-> o----- <- horizontal line
Now, after knowing the angle, we will use equation of a circle adjusted to ellipse, to recalculate new icon's coordinates to stick our icon exactly to that ellipse. Let's go to the algorithm.
To detect touch angle, we will use atan2 function.
double atan2Angle = Math.atan2(touchY - centerY, touchX - centerX);
This will give us following values depending on the angle.
-π
-2*π -0
o <- center
2*π 0
π
To be able to use this angle in equation of a circle we need to convert it into more traditional presentation like the one below.
270°
180° o 0°/360°
90°
It can be done like this.
float angleFactor = (float) (atan2Angle / 2 * Math.PI);
if (angleFactor < 0) {
angleFactor += 1f;
}
float touchAngle = angleFactor * 360f;
if (angle < 0) {
angle += 360f;
}
Now, after we've got our touch angle in degree, we can calculate new coordinates for our icon using equation of an ellipse.
double touchAngleRad = Math.toRadians(touchAngle);
float iconX = centerX + (float) (radiusX * Math.cos(touchAngleRad));
float iconY = centerY + (float) (radiusY * Math.sin(touchAngleRad));
// centerX, centerY - coordinates of the center of ellipses
// radiusX, radiusY - horizontal and vertical radiuses of the ellipse, to which
// the touched icon belongs
// iconX, iconY - new coordinates of the icon lying on that ellipse
If you recalculate icon position according to this algorithm on every TOUCH_MOVE and invalidate the view, then your icon will move across its ellipse.
The code can be further optimized by using radians instead of degrees, but I thought degrees are better suitable for explanation. Hope this help. Post your code, if you experience any issues with the implementation.
I am trying to gain some more familiarity with the Android SurfaceView class, and in doing so am attempting to create a simple application that allows a user to move a Bitmap around the screen. The troublesome part of this implementation is that I am also including the functionality that the user may drag the image again after it has been placed. In order to do this, I am mapping the bitmap to a simple set of coordinates that define the Bitmap's current location. The region I am mapping the image to, however, does not match up with the image.
The Problem
After placing an image on the SurfaceView using canvas.drawBitmap(), and recording the coordinates of the placed image, the mapping system that I have set up misinterprets the Bitmap's coordinates somehow and does not display correctly. As you can see in this image, I have simply used canvas.drawLine() to draw lines representing the space of my touch region, and the image is always off and to the right:
The Code
Here, I shall provide the relevant code excerpts to help answer my question.
CustomSurface.java
This method encapsulates the drawing of the objects onto the canvas. The comments clarify each element:
public void onDraw(Canvas c){
//Simple black paint
Paint paint = new Paint();
//Draw a white background
c.drawColor(Color.WHITE);
//Draw the bitmap at the coordinates
c.drawBitmap(g.getResource(), g.getCenterX(), g.getCenterY(), null);
//Draws the actual surface that is receiving touch input
c.drawLine(g.left, g.top, g.right, g.top, paint);
c.drawLine(g.right, g.top, g.right, g.bottom, paint);
c.drawLine(g.right, g.bottom, g.left, g.bottom, paint);
c.drawLine(g.left, g.bottom, g.left, g.top, paint);
}
This method encapsulates how I capture touch events:
public boolean onTouchEvent(MotionEvent e){
switch(e.getAction()){
case MotionEvent.ACTION_DOWN:{
if(g.contains((int) e.getX(), (int) e.getY()))
item_selected = true;
break;
}
case MotionEvent.ACTION_MOVE:{
if(item_selected)
g.move((int) e.getX(), (int) e.getY());
break;
}
case MotionEvent.ACTION_UP:{
item_selected = false;
break;
}
default:{
//Do nothing
break;
}
}
return true;
}
Graphic.java
This method is used to construct the Graphic:
//Initializes the graphic assuming the coordinate is in the upper left corner
public Graphic(Bitmap image, int start_x, int start_y){
resource = image;
left = start_x;
top = start_y;
right = start_x + image.getWidth();
bottom = start_y + image.getHeight();
}
This method detects if a user is clicking inside the image:
public boolean contains(int x, int y){
if(x >= left && x <= right){
if(y >= top && y <= bottom){
return true;
}
}
return false;
}
This method is used to move the graphic:
public void move(int x, int y){
left = x;
top = y;
right = x + resource.getWidth();
bottom = y + resource.getHeight();
}
I also have 2 methods that determine the center of the region (used for redrawing):
public int getCenterX(){
return (right - left) / 2 + left;
}
public int getCenterY(){
return (bottom - top) / 2 + top;
}
Any help would be greatly appreciated, I feel as though many other StackOverflow users could really benefit from a solution to this issue.
There's a very nice and thorough explanation of touch/multitouch/gestures on Android Developers blog, that includes free and open source code example at google code.
Please, take a look. If you don't need gestures -- just skip that part, read about touch events only.
This issue ended up being much simpler than I had thought, and after some tweaking I realized that this was an issue of image width compensation.
This line in the above code is where the error stems from:
c.drawBitmap(g.getResource(), g.getCenterX(), g.getCenterY(), null);
As you can tell, I manipulated the coordinates from within the Graphic class to produce the center of the bitmap, and then called canvas.drawBitmap() assuming that it would draw from the center outward.
Obviously, this would not work because the canvas always drops from the top left of an image downwards and to the right, so the solution was simple.
The Solution
Create the touch region with regards to the touch location, but draw it relative to a distance equal to the image width subtracted from the center location in the x and y directions. I basically changed the architecture of the Graphic class to implement a getDrawX() and getDrawY() method that would return the modified x and y coordinates of where it should be drawn in order to have the center_x and center_y values (determined in the constructor) actually appear to be at the center of the region.
It all comes down to the fact that in an attempt to compensate for the way the canvas draws bitmaps, I unfortunately incorporated some bad behaviors and in the end had to handle the offset in a completely different way.
I want to scale view on touch event of ACTION_MOVE.
all bitmap are in square format. (point1= top+left, point2 =top+right, point3=bottom+left, point4=bottom+right)
i got the all four points.
when i drag the yellow gripper on top of the bitmap , red ball resize according to that (gripper can be move at any direction).
my question:
--> how should i calculate the distance of gripper center point and bitmap (left+top) point1 and scale according to bitmap.
means if i drag the bitmap from top+left corner then it scale/resize from point1,point2,point4 only (bottom+right corner remail at its position).
--> i am using canvas to draw bitmap, is it the rightway to handle bitmap scale/rotate ?
You can get the x,y coordinates on the event ACTION_DOWN and the x',y' coordinates on ACTION_UP. With the two points, you can make your measures (Euclidean Distance, for example).
gripper.setOnTouchListener(new OnTouchListener(){
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == android.view.MotionEvent.ACTION_DOWN){
x1 = event.getX();
y1 = event.getY();
}
else if(event.getAction() == android.view.MotionEvent.ACTION_UP){
x2 = event.getX();
y2 = event.getY();
}
return false;
}
});
You can also get the left,top position of the bitmap with getLeft(),getTop() methods (but note that these methods return the position relative to the View's parent layout).
There is an Matrix parameter on the createBitmap method that will allow you to resize your Bitmap and so you can store it with the resolution you want. See how here (it's an excellent tutorial, by the way). You probably already know how to do that, but I see no problem in sharing it anyway =P.
Hope it helps