2011-02-27 21 views
5

Tengo una necesidad de tocar y arrastrar una UIView en la pantalla con desaceleración. He escrito el código muy bien para mover la vista con toques, pero necesito que el objeto siga moviéndose con un cierto grado de inercia (una vez que se alcanza cierto umbral de aceleración), desacelerando hasta que se detiene o alcanza el límite de la pantalla. Esto no es para un juego, sino que usa algunos controles UIView estándar. La parte más importante con la que estoy lidiando es la aceleración.Mover UIView con desaceleración

¿Algún buen algoritmo que haya escrito para lograr lo mismo?

Editar:

estoy usando un bloque de animación en el método touchesEnded:, pero hay un retraso notable entre el momento en que una persona deja para ir de su dedo y las patadas de animación en:

[UIView transitionWithView:self 
        duration:UI_USER_INTERFACE_IDIOM() == 
           UIUserInterfaceIdiomPhone ? 0.33f : 0.33f * 2.0f 
        options:UIViewAnimationCurveEaseOut 
       animations:^(void){ 
         if (dir == 1) // Flicked left 
         { 
          self.center = CGPointMake(self.frame.size.width * 0.5f, 
                 self.center.y); 
         } 
         else { // Flicked right 
          self.center = CGPointMake(
           self.superview.bounds.size.width - 
            (self.frame.size.width * 0.5f), self.center.y); 
         } 
        } 
       completion:^(BOOL finished){ 
        // Do nothing 
       }]; 

Respuesta

6

El problema está en la función de temporización utilizada para la animación. La animación debe ser tan rápida como la del usuario al arrastrar la primera y desacelera rápidamente. El siguiente código muestra un ejemplo muy simple de implementación de este comportamiento.

Primero, en mi método touchesBegan:withEvent:, grabé la primera ubicación táctil en mi buffer de puntos. Estoy almacenando dos ubicaciones táctiles para obtener el vector de movimiento de la vista, y podría haber diferentes formas de obtener el vector.

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 
    ivar_lastPoint[0] = [[touches anyObject] locationInView:self]; 
    ivar_lastPoint[1] = ivar_lastPoint[0]; 
    ivar_touchOffset.x = ivar_lastPoint[0].x - self.sprite.position.x; 
    ivar_touchOffset.y = ivar_lastPoint[0].y - self.sprite.position.y; 
    self.lastTime = [NSDate date]; 
} 

Luego, en touchesMoved:withEvent: método, he actualizado la ubicación de la vista. En realidad, utilicé un CALayer en lugar de una vista, ya que quiero utilizar una función de temporización personalizada para su animación. Entonces, actualizo la ubicación de la capa de acuerdo con el usuario, y durante un intervalo dado, actualizo los búferes de ubicación.

#define kSampleInterval 0.02f 

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { 

    [CATransaction begin]; 
    [CATransaction setDisableActions:YES]; 
    /* First of all, move the object */ 
    CGPoint currentPoint = [[touches anyObject] locationInView:self]; 
    CGPoint center = self.sprite.position; 
    center.x = currentPoint.x - ivar_touchOffset.x; 
    center.y = currentPoint.y - ivar_touchOffset.y; 
    self.sprite.position = center; 

    /* Sample locations */ 
    NSDate *currentTime = [NSDate date]; 
    NSTimeInterval interval = [currentTime timeIntervalSinceDate:self.lastTime]; 
    if (interval > kSampleInterval) { 
     ivar_lastPoint[0] = ivar_lastPoint[1]; 
     ivar_lastPoint[1] = currentPoint; 
     self.lastTime = currentTime; 
     self.lastInterval = interval; 

    } 
    [CATransaction commit]; 
} 

self.sprite es una referencia al objeto CALayer en mi opinión. No necesito animaciones para arrastrar, así que lo desactivé usando el objeto de clase CATransaction.

Finalmente, calculo el vector y aplico la animación en el método touchesEnded:withEvent:.Aquí, creé una función CAMediaTimingFunction personalizada, por lo que es realmente "entrada rápida, salida fácil".

#define kDecelerationDuration 1.0f 
#define kDamping 5.0f 

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 
    CGPoint targetPoint; 
    NSDate *currentTime = [NSDate date]; 
    NSTimeInterval interval = self.lastInterval + [currentTime timeIntervalSinceDate:self.lastTime]; 
    targetPoint.x = self.sprite.position.x + (ivar_lastPoint[1].x - ivar_lastPoint[0].x)/interval*kDecelerationDuration/kDamping; 
    targetPoint.y = self.sprite.position.y + (ivar_lastPoint[1].y - ivar_lastPoint[0].y)/interval*kDecelerationDuration/kDamping; 
    if (targetPoint.x < 0) { 
     targetPoint.x = 0; 
    } else if (targetPoint.x > self.bounds.size.width) { 
     targetPoint.x = self.bounds.size.width; 
    } 
    if (targetPoint.y < 0) { 
     targetPoint.y = 0; 
    } else if (targetPoint.y > self.bounds.size.height) { 
     targetPoint.y = self.bounds.size.height; 
    } 
    CAMediaTimingFunction *timingFunction = [CAMediaTimingFunction functionWithControlPoints: 
              0.1f : 0.9f :0.2f :1.0f]; 

    [CATransaction begin]; 
    [CATransaction setValue:[NSNumber numberWithFloat:kDecelerationDuration] forKey:kCATransactionAnimationDuration]; 
    [CATransaction setAnimationTimingFunction:timingFunction]; 

    self.sprite.position = targetPoint; 
    [CATransaction commit]; 

} 

Este es un ejemplo muy simple. Es posible que desee un mejor mecanismo de obtención de vectores. Además, esto solo mueve un componente visual (CALayer). Probablemente necesites un objeto UIView para manejar eventos del objeto. En este caso, es posible que desee animar a través de CALayer y mover el objeto UIView real por separado. Podría haber múltiples formas de manejar la animación CALayer y la reubicación UIView juntas.

+0

Código de ejemplo, por favor? –

+0

Me di cuenta de que la animación en 'touchesMoved: withEvent:' no funcionaría para su caso, porque en este caso, no debe comenzar la animación antes de que el usuario se quite un dedo. Sin embargo, también me di cuenta de que la diferencia de tiempo entre el momento en que se compromete una animación y cuándo realmente comienza no debería importar simplemente mover una vista. El problema es más bien en la función de tiempo, por lo que el comienzo de la animación debe ser lo suficientemente rápido como para sentir que el movimiento es continuo. Vea mi respuesta actualizada para el código. – MHC

0

Puede usar una aceleración constante para que el objeto deje de moverse a una velocidad constante de desaceleración. O puede hacer que la aceleración aumente o disminuya dependiendo de si desea que la desaceleración sea más rápida/más lenta hacia el punto de alcanzar la velocidad cero.

struct Velocity { 
float x; 
float y; } 

Vector acceleration = your acceleration equation. // can be constant

Vector newVelocity = oldVelocity + acceleration * timeDelta;

Vector newPosition = newVelocity * timeDelta;

suponiendo que tiene un vector normalizado para su sentido de la marcha, y sólo está Contrarrestar esa dirección se puede utilizar en lugar de flotación del vector.

Debe enlazar la nuevaPosición en el borde. Y tiene que dejar de iterar por delta de tiempo cuando la velocidad se vuelve negativa.

+0

Esa es una gran respuesta para crear vectores, pero ¿cómo la implemento en el contexto de un UIView y eventos táctiles? Mi implementación actual toma una marca de tiempo de cuándo comienzan los toques y cuándo terminan, más la posición de la vista cuando comienzan los toques y cuando termina. Luego uso un bloque de animación para animar la vista de un extremo al otro, pero hay un retraso notable desde el momento en que el toque finaliza y cuando se ejecuta la animación. ¿Cómo puedo hacer eso más fluido? –

3

Usar Core Animation. Es bastante sencillo si mira los documentos para UIView: cree una animación que establezca la posición final de la vista y especifique UIViewAnimationOptionCurveEaseOut como la curva de sincronización. Todo esto te llevará solo un puñado de líneas para implementar.

+0

Ya tengo esa parte, pero mantener la aceleración es un poco más complicada. Mira el comentario que dejé para la publicación de Scott. –

+0

Genial, por lo que parece que ya está utilizando Core Animation para mover la vista. Cuando el toque finaliza, crea una animación más como la anterior. Escala tanto la distancia desde la ubicación actual a la ubicación final como la duración de la animación de acuerdo con la velocidad actual de la vista. Supongo que se trata de hacer que se vea mejor que tener un modelo de física preciso, pero si quieres tener más control sobre la curva de desaceleración, puedes hacer una serie corta de animaciones vinculadas. – Caleb

Cuestiones relacionadas