Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can i pass Touch Listeners to Custom View for Drag and Drop?

I've a question concerning handling touch events for CustomView.I'm adding custom view's dynamically to layout (i.e FrameLayout). Those custom views having touchListeners for pulling points at corners (It shows in the below image). Along with that i have to drag and drop the total view on the screen, if user touches other than those corner points (color area in the image) have to drag and drop of the view otherwise not, and also if user touches outside of that view i don't want trigger any touch listeners.

Check This Image

I am able to pull those points by using this code

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:

        if (topTouchArea.contains(event.getX(), event.getY())) {                
            currentTouch = TOUCH_TOP;
        } else if (RightTouchArea.contains(event.getX(),event.getY())) {                
            currentTouch = TOUCH_RIGHT;
        } else if (LeftTouchArea.contains(event.getX(),event.getY())) {            
            currentTouch = TOUCH_LEFT;
        } else {
            return false; //Return false if user touches none of the corners
        }
        return true; 
    case MotionEvent.ACTION_MOVE:

        switch (currentTouch) {
        case TOUCH_TOP:              
             top.x = event.getX();
             top.y = event.getY();                            
             invalidate();
             return true;
        case TOUCH_RIGHT:                
             Right.x = event.getX();
             Right.y = event.getY();                
             invalidate();
             return true;
        case TOUCH_LEFT:                 
             Left.x = event.getX();
             Left.y = event.getY();             
             invalidate();
             return true;       
        }         

    case MotionEvent.ACTION_UP:

        switch (currentTouch) {
        case TOUCH_TOP:
             top.x = event.getX();
             top.y = event.getY();                
             invalidate();
             currentTouch = NONE;
             return true;
        case TOUCH_RIGHT:
             Right.x = event.getX();
             Right.y = event.getY();             
             invalidate();
             currentTouch = NONE;
             return true;
        case TOUCH_LEFT:
            Left.x = event.getX();
             Left.y = event.getY();               
             invalidate();
             currentTouch = NONE;
             return true;      
        }         
        return false;
    }
    return false;
}

How can i achieve this drag and drop along with above characters of the CustomView....

like image 296
RajaReddy PolamReddy Avatar asked Jun 21 '12 11:06

RajaReddy PolamReddy


2 Answers

I'd use some linear algebra to solve this problem. You have 3 or more points which are not part of a line (linear independent). You could use this fact to determine if the touch point lies within the area. This gets more difficult the more points you have, So you need to transform polygons with more corners than 3 down to a set of triangles, applying the following procedure.

So you have three points A,B,C defining a triangle. Then you define three lines using your touch point to get the direction vectors AT,BT,CT while the A,B,Cs denote the base points of these three touch lines.

Then you define boundary lines AB with the base point A, AC with base point A (it does not matter if A or C is the base point) and at last the line CB with the base point C. So lets summarize (large letters are denoting vectors while small letters are denoting scalars or factors):

Boundary lines:

a:X=A+f1*AB
b:X=B+f2*BC
c:X=C+f3*AC

Touch lines:

ta:X=A+t1*AT
tb:X=B+t2*BT
tc:X=C+t3*CT

Resulting triangle with touch lines and intersections

Now you intersect the touch lines (pink in in the figure) with the border lines (green/red) in opposite to base point of the touch line. For example you need to intersect line 'ta' with line b to get intersection point I3. This point you'll reach if you extent the vector AT by the factor t1.

If this factor t1 is greater than 1 the touch point T lies between A and I3 one the line 'ta'. if The factor t1 is lesser than 1 T lies outside of the line part A-I3.

You will have to do this three times with line tb and c and tc and a. Each time the I(n) points will need to be lesser than one (where is n is in {1,2,3}).

If this condition applies for all three intersections your touch point is inside the triangle.

You will get the Intersection by solving these simple systems of equations:

The first

A+t1*AT = B+f2*BC
<=>
0 = A+t1*AT - B - f2*BC

This systems looks like this (where x,y donate the specific coordinate component):

0 = xA + t1*(xT-xA) - xB - f2*(xC-xB)
0 = yA + t1*(yT-yA) - yB - f2*(yC-yB)

The second

B+t2*BT = C+f3*AC
<=>
0 = B+t2*BT - C - f3*AC

The third

C+t3*CT = A+f1*AB
<=>
0 = C+t3*CT - A - f1*AB

Solving these three systems will give you the answer if a point lies within the area or not. The solution needs to be distinctly from 0 (If 0 is the only solution your points are on the same line -> linear dependent)!

Based upon that you can translate your all of the coordinates of your object.

As mentioned above you'll need to reduce down to triangles and apply this method to all subtriangles of a polygon. If one matches the condition (or two or more if you want to implement multitouching) your touch is within the object area.

This are just three small computations and should be called in the "Touch-Down" Event Handler once. Since you know your you touched the area there is no need to do all these computations while moving your finger(s).

About Trianglization This is a more complex matter not answered in one sentence. According to your comments on the other answer you do not want to handle simple polygons, you want to handle more complex shapes consisting of polygons. As for example your star shape. Here you can just use the method above, you need to define your triangles yourself. Because its not that simple to know which corner belongs to a face and which not.

An alternate solution would be using the so called Quick Hull algorithm to generate a convex hull from a point set. This will give you the contour of the shape which corner points you should use to trianglelize. If you have some "unshaded" faces just handle them as touched as well, if you really want to handle all possible point sets.

My Solution in you star case:

  • Define you spike triangles

  • use the method I mentioned earlier above (by solving some LSEs) to determine if one is touched or not.

For everything else:

Trianglize it by using circumcircle of a triangle. By finding three points which defining a circle not containing any other contour points. The so called Delaunay Triangulation. Let this method find your triangles. If you have some unshaded areas you could define a triangle object with the property "touchable" or something like that, to let your algorithm know if the touch should be handled or not.

If you handle and update you set of triangles properly there should be no more questions about how to handle a concave shape.

For your pentagon try following:

  • use the the quick hull and triangulation method.

  • if a user changes a points position, update triangles

  • profit

like image 140
kneo Avatar answered Nov 18 '22 10:11

kneo


First of all, you need to override View.dispatchTouchEvent() to filter out-of-triangle touches. Check out my answer here for explanation on how to do that and why you need to use that particular method and not OnTouchListener to make parts of the view touch-transparent.

Then, in your OnTouchListener, you'll want to compare touch coordinates to the ones of your triangle points to figure out whether user hit one of the points or the inside of the triangle. I also suggest you give the points a little margin there - it's pretty hard to point little objects on touch screens, so let the user have like an 4-8 pixel mistake buffer for that.

So, if the user hits a point - drag the point, if they hit the inside of the triangle and not one of the points (i.e. touch point is within the triangle but is not within one of the points' localities) - drag the whole view. Also, If the triangle is so small that point margins overlap (which will result in a touch matching more than one point locality), just pick the closest one.

In case you're not familiar with drag-and-drop api, here's a nice tutorial. You'll need to call startDrag() from your OnTouchListener if the action is ACTION_DOWN and the touch point is inside the triangle.

For pre-honeycomb APIs, there's a drag-and-drop library called android-dragarea. There's a link to example app in documentation.

UPD Oh, I didn't realize you were looking for algorithms also. You want a Point-in-Polygon algorithm , here are some nice solutions, they'll work for almost any complex polygon:

  • Ray Casting Algorithm implementation 1: http://chandan-tech.blogspot.com/2010/12/whether-point-lies-inside-polygon-2d.html (There's complete code, works right away)
  • Ray Casting Algorithm implementation 2: This is a slightly different implementation of the first one: http://www.anddev.org/other-coding-problems-f5/using-java-awt-polygon-in-android-t6521.html (complete code, works right away)
  • This one is a bit more complex in implementation and will take more RAM, but I believe it may give you advantage in speed over the first two, since it's Bitmap-based: https://stackoverflow.com/a/2597702/375929
like image 29
Ivan Bartsov Avatar answered Nov 18 '22 10:11

Ivan Bartsov