web-dev-qa-db-fra.com

Animation UIView basée sur la vitesse UIPanGestureRecognizer

J'aimerais pouvoir déplacer une sous-vue sur et hors de l'écran, tout comme vous parcourez les images dans l'application Photos de l'iPhone, donc si la sous-vue est plus de 1/2 hors écran lorsque je lâche avec mon doigt, elle doit animer hors écran, mais il doit également prendre en charge le balayage, donc si la vitesse de balayage/panoramique est suffisamment élevée, il doit animer hors écran, même s'il est inférieur à 1/2 hors écran.

Mon idée était d'utiliser UIPanGestureRecognizer puis de tester la vitesse. Cela fonctionne, mais comment puis-je définir une durée d'animation correcte pour le déplacement de l'UIView en fonction de l'emplacement actuel de la vue et de la vitesse du panoramique afin qu'elle semble fluide? Si je fixe une valeur fixe, l'animation commence à ralentir ou à accélérer par rapport à la vitesse de balayage de mes doigts.

36
Neigaard

Les docs disent

La vitesse du mouvement panoramique, qui est exprimée en points par seconde. La vitesse est divisée en composantes horizontales et verticales.

Donc je dirais, étant donné que vous voulez déplacer votre vue xPoints (mesurée en pt) pour la laisser disparaître de l'écran, vous pouvez calculer la durée de ce mouvement comme suit:

CGFloat xPoints = 320.0;
CGFloat velocityX = [panRecognizer velocityInView:aView].x;
NSTimeInterval duration = xPoints / velocityX;
CGPoint offScreenCenter = moveView.center;
offScreenCenter.x += xPoints;
[UIView animateWithDuration:duration animations:^{
    moveView.center = offScreenCenter;
}];

Vous voudrez peut-être utiliser + (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion à la place et essayer différents UIViewAnimationOptions.

54
Dennis

Une observation sur le fait de compter sur la vitesse dans UIPanGestureRecognizer: Je ne connais pas votre expérience, mais j'ai trouvé que la vitesse générée par le système sur le simulateur n'était pas terriblement utile. (C'est correct sur l'appareil, mais problématique sur le simulateur.)

Si vous effectuez un panoramique rapide et que vous vous arrêtez brusquement, attendez, puis terminez le geste (par exemple, l'utilisateur commence un balayage, se rend compte que ce n'était pas ce qu'il voulait, alors il s'arrête puis relâche son doigt), la vitesse signalée par velocityInView: à l'état UIGestureRecognizerStateEnded lorsque votre doigt a été relâché semble être la vitesse rapide avant que je ne m'arrête et n'attende, tandis que la bonne vitesse dans cet exemple serait nulle (ou proche de zéro). En bref, la vitesse signalée est celle qu'elle était juste avant la fin du panoramique, mais pas la vitesse à la fin du panoramique elle-même.

J'ai fini par calculer la vitesse moi-même, manuellement. (Il semble stupide que cela soit nécessaire, mais je ne voyais aucun moyen de contourner cela si je voulais vraiment obtenir la vitesse finale du panoramique.) En bout de ligne, lorsque l'état est UIGestureRecognizerStateChanged je garde une trace de le CGPoint translationInView actuel et précédent ainsi que l'heure, puis en utilisant ces valeurs lorsque j'étais dans le UIGestureRecognizerStateEnded pour calculer la vitesse finale réelle. Cela fonctionne plutôt bien.

Voici mon code pour calculer la vitesse. Il se trouve que je n'utilise pas la vélocité pour déterminer la vitesse de l'animation, mais je l'utilise plutôt pour déterminer si l'utilisateur a effectué un panoramique suffisamment éloigné ou un mouvement assez rapide pour que la vue se déplace de plus de la moitié de l'écran et donc déclenchant l'animation entre les vues, mais le concept de calcul de la vitesse finale semble applicable à cette question. Voici le code:

- (void)handlePanGesture:(UIPanGestureRecognizer *)gesture
{
    static CGPoint lastTranslate;   // the last value
    static CGPoint prevTranslate;   // the value before that one
    static NSTimeInterval lastTime;
    static NSTimeInterval prevTime;

    CGPoint translate = [gesture translationInView:self.view];

    if (gesture.state == UIGestureRecognizerStateBegan)
    {
        lastTime = [NSDate timeIntervalSinceReferenceDate];
        lastTranslate = translate;
        prevTime = lastTime;
        prevTranslate = lastTranslate;
    }
    else if (gesture.state == UIGestureRecognizerStateChanged)
    {
        prevTime = lastTime;
        prevTranslate = lastTranslate;
        lastTime = [NSDate timeIntervalSinceReferenceDate];
        lastTranslate = translate;

        [self moveSubviewsBy:translate];
    }
    else if (gesture.state == UIGestureRecognizerStateEnded)
    {
        CGPoint swipeVelocity = CGPointZero;

        NSTimeInterval seconds = [NSDate timeIntervalSinceReferenceDate] - prevTime;
        if (seconds)
        {
            swipeVelocity = CGPointMake((translate.x - prevTranslate.x) / seconds, (translate.y - prevTranslate.y) / seconds);
        }

        float inertiaSeconds = 1.0;  // let's calculate where that flick would take us this far in the future
        CGPoint final = CGPointMake(translate.x + swipeVelocity.x * inertiaSeconds, translate.y + swipeVelocity.y * inertiaSeconds);

        [self animateSubviewsUsing:final];
    }
}
21
Rob

Dans la plupart des cas, la définition de l'option UIViewAnimationOptionBeginFromCurrentState pour l'animateur UIView suffit pour faire une animation parfaitement continue.

3
nikans