Domanda

I want to make a custom gesture recognizer with three fingers. Which is similar to unpinch gesture recognizer.

All I need is an idea about how to recognize it.

My gesture needs to recognize three fingers with three directions. For example:

enter image description here

enter image description here

enter image description here

I hope images makes sense. I need to make it flexible for any three opposite directions. Thanks in advance. Any help would be appreciated.

I am aware about the subclass methods and I've created custom gestures already with single finger like semicircle, full circle. I need a coding idea about how to handle that.

È stato utile?

Soluzione 2

Quick note for future readers: the way you do an unpinch/pinch with three fingers is add the distances ab,bc,ac.

However if your graphics package just happens to have on hand "area of a triangle" - simply use that. ("It saves one whole line of code!")

Hope it helps.


All you need to do is track:

the distance between the three fingers!

Simply add up "every" permutation

(Well, there's three .. ab, ac and cb. Just add those; that's all there is to it!)

When that value, say, triples from the start value, that's an "outwards triple unpinch".

... amazingly it's that simple.

Angles are irrelevant.


Footnote if you want to be a smartass: this applies to any pinch/unpinch gesture, 2, 3 fingers, whatever:

track the derivative of the sum-distance (I mean to say, the velocity) rather than the distance. (Bizarrely this is often EASIER TO DO! because it is stateless! you need only look at the previous frame!!!!)

So in other words, the gesture is trigger when the expansion/contraction VELOCITY of the fingers reaches a certain value, rather than a multiple of the start value.


More interesting footnote!

However there is a subtle problem here: whenever you do anything like this (any platform) you have to be careful to measure "on the glass".

IF You are just doing distance (ie, my first solution above) of course everything cancels out and you can just say "if it doubles" (in pixels -- points -- whatever the hell). BUT if you are doing velocity as part of the calculation in any gesture, then somewhat surprisingly, you have to literally find the velocity in meters per second in the real world, which sounds weird at first! Of course you can't do this exactly (particularly with android) coz glass sizes vary somewhat, but you have to get close to it. Here is a long post discussing this problem http://answers.unity3d.com/questions/292333/how-to-calculate-swipe-speed-on-ios.html In practice you usually have to make do with "screen-widths-per-second" which is pretty good. (But this may be vastly different on phones, large tablets, and these days "surface" type things. on your whole iMac screen, 0.1 screenwidthspersecond may be fast, but on an iPhone that is nothing, not a gesture.)


Final footnote! I simply don't know if Apple use "distance multiple" or "glass velocity" in their gesture recognition, or also likely is some subtle mix. I've never read an article from them commenting on it.


Another footnote! -- if for whatever reason you do want to find the "center" of the triangle (I mean the center of the three fingers). This is a well-travelled problem for game programmers because, after all, all 3D mesh is triangles.

Fortunately it's trivial to find the center of three points, just add the three vectors and divide by three! (Confusingly this even works in higher dimensions!!)

You can see endless posts on this issue...

http://answers.unity3d.com/questions/445442/calculate-uv-at-center-of-triangle.html

http://answers.unity3d.com/questions/424950/mid-point-of-a-triangle.html

Conceivably, if you were incredibly anal, you would want the "barycenter" which is more like the center of mass, just google if you want that.

Altri suggerimenti

You need to create a UIGestureRecognizer subclass of your own (let's call it DRThreeFingerPinchGestureRecognizer) and in it to implement:

– touchesBegan:withEvent:
– touchesMoved:withEvent:
– touchesEnded:withEvent:
– touchesCancelled:withEvent:

These methods are called when touches are accepted by the system and possibly before they are sent to the view itself (depending on how you setup the gesture recognizer). Each of these methods will give you a set of touches, for which you can check the current location in your view and previous location. Since pinch gesture is relatively very simple, this information is enough for you to test if the user is performing a pinch, and fail the test (UIGestureRecognizerStateFailed). If state was not failed by – touchesEnded:withEvent:, you can recognize the gesture.

I say pinch gestures are simple, because you can easily track each touch and see how it moves compared to other touches and itself. If a threshold of an angle is passed and broken, you fail the test, otherwise you allow it to continue. If touches do not move in separate angles to each other, you fail the test. You will have to play with what angles of the vectors are acceptable, because 120 degrees are not optimal for the three most common fingers (thumb + index + middle fingers). You may just want to check that the vectors are not colliding.

Make sure to read the UIGestureRecognizer documentation for an in-depth look at the various methods, as well as subclassing notes.

I think track angles is leading you down the wrong path. I think it's likely a more flexible and intuitive gesture if you don't constrain it based on the angles between the fingers. It'll be less error prone if you just deal with it as a three-fingered pinch regardless of how the fingers move relative to each other. This is what I'd do:

if(presses != 3) {
     state = UIGestureRecognizerStateCancelled;
     return;
}

// After three fingers are detected, begin tracking the gesture.
state =  UIGestureRecognizerStateBegan; 
central_point_x = (point1.x + point2.x + point3.x) / 3;
central_point_y = (point1.y + point2.y + point3.y) / 3;

// Record the central point and the average finger distance from it.
central_point = make_point(central_point_x, central_point_y);
initial_pinch_amount = (distance_between(point1, central_point) + distance_between(point2, central_point) + distance_between(point3, central_point)) / 3;

Then on each update for touches moved:

if(presses != 3) {
     state = UIGestureRecognizerStateEnded;
     return;
}

// Get the new central point
central_point_x = (point1.x + point2.x + point3.x) / 3;
central_point_y = (point1.y + point2.y + point3.y) / 3;
central_point = make_point(central_point_x, central_point_y);

// Find the new average distance
pinch_amount = (distance_between(point1, central_point) + distance_between(point2, central_point) + distance_between(point3, central_point)) / 3;

// Determine the multiplicative factor between them.
difference_factor = pinch_amount / initial_pinch_amount

Then you can do whatever you want with the difference_factor. If it's greater than 1, then the pinch has moved away from the center. If it's less than one, it's moved towards the center. This will also give the user the ability to hold two fingers stationary and only move a third to perform your gesture. This will address certain accessibility issues that your users may encounter.

Also, you could always track the incremental change between touch move events, but they won't be equally spaced in time and I suspect you'll have more troubles dealing with it.

I also apologize for the pseudo-code. If something isn't clear I can look at doing up a real example.

Simple subclass of UIGestureRecognizer. It calculates the relative triangular center of 3 points, and then calculates the average distance from that center, angle is not important. You then check the average distance in your Gesture Handler.

.h

#import <UIKit/UIKit.h>
#import <UIKit/UIGestureRecognizerSubclass.h>
@interface UnPinchGestureRecognizer : UIGestureRecognizer
 @property CGFloat averageDistanceFromCenter;
@end

.m

#import "UnPinchGestureRecognizer.h"
@implementation UnPinchGestureRecognizer

-(CGPoint)centerOf:(CGPoint)pnt1 pnt2:(CGPoint)pnt2 pnt3:(CGPoint)pnt3
{
    CGPoint center;
    center.x = (pnt1.x + pnt2.x + pnt3.x) / 3;
    center.y = (pnt1.y + pnt2.y + pnt3.y) / 3;
    return center;
}
-(CGFloat)averageDistanceFromCenter:(CGPoint)center pnt1:(CGPoint)pnt1 pnt2:(CGPoint)pnt2 pnt3:(CGPoint)pnt3
{
    CGFloat distance;
    distance = (sqrt(fabs(pnt1.x-center.x)*fabs(pnt1.x-center.x)+fabs(pnt1.y-center.y)*fabs(pnt1.y-center.y))+
                sqrt(fabs(pnt2.x-center.x)*fabs(pnt2.x-center.x)+fabs(pnt2.y-center.y)*fabs(pnt2.y-center.y))+
                sqrt(fabs(pnt3.x-center.x)*fabs(pnt3.x-center.x)+fabs(pnt3.y-center.y)*fabs(pnt3.y-center.y)))/3;
    return distance;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    if ([touches count] == 3) {
        [super touchesBegan:touches withEvent:event];
        NSArray *touchObjects = [touches allObjects];
        CGPoint pnt1 = [[touchObjects objectAtIndex:0] locationInView:self.view];
        CGPoint pnt2 = [[touchObjects objectAtIndex:1] locationInView:self.view];
        CGPoint pnt3 = [[touchObjects objectAtIndex:2] locationInView:self.view];

        CGPoint center = [self centerOf:pnt1 pnt2:pnt2 pnt3:pnt3];
        self.averageDistanceFromCenter = [self averageDistanceFromCenter:center pnt1:pnt1 pnt2:pnt2 pnt3:pnt3];
        self.state = UIGestureRecognizerStateBegan;
    }
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {

    if ([touches count] == 3)
    {
        NSArray *touchObjects = [touches allObjects];
        CGPoint pnt1 = [[touchObjects objectAtIndex:0] locationInView:self.view];
        CGPoint pnt2 = [[touchObjects objectAtIndex:1] locationInView:self.view];
        CGPoint pnt3 = [[touchObjects objectAtIndex:2] locationInView:self.view];

        CGPoint center = [self centerOf:pnt1 pnt2:pnt2 pnt3:pnt3];
        self.averageDistanceFromCenter = [self averageDistanceFromCenter:center pnt1:pnt1 pnt2:pnt2 pnt3:pnt3];
        self.state = UIGestureRecognizerStateChanged;
        return;
    }

}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesEnded:touches withEvent:event];
    self.state = UIGestureRecognizerStateEnded;
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesEnded:touches withEvent:event];
    self.state = UIGestureRecognizerStateFailed;
}

@end

implementation of Gesture, I have a max avg distance set to start, and then a minimum to end, you can also check during changed as well:

-(IBAction)handleUnPinch:(UnPinchGestureRecognizer *)sender
{
    switch (sender.state) {
        case UIGestureRecognizerStateBegan:
            //If you want a maximum starting distance
            self.validPinch = (sender.averageDistanceFromCenter<75);
            break;
        case UIGestureRecognizerStateEnded:
            //Minimum distance from relative center
            if (self.validPinch && sender.averageDistanceFromCenter >=150) {
                NSLog(@"successful unpinch");
            }
            break;
        default:
            break;
    }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top