Переместить UIView с замедлением - PullRequest
5 голосов
/ 27 февраля 2011

Мне нужно нажать и перетащить UIView на экране с замедлением.Я очень хорошо написал код для перемещения вида касаниями, но мне нужно, чтобы объект продолжал двигаться с определенной степенью инерции (как только достигнут определенный порог ускорения), замедляясь до тех пор, пока он не остановится или не достигнет границы экрана.Это не для игры, а с использованием некоторых стандартных элементов управления UIView.Самая большая часть, с которой я сталкиваюсь - это ускорение.

Есть ли хорошие алгоритмы, которые вы написали для достижения того же самого?

Редактировать:

Я использую блок анимации наtouchesEnded:, но есть заметная задержка между временем, когда человек отпускает палец, и анимацией запускается:

[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
                }];

Ответы [ 3 ]

6 голосов
/ 09 марта 2011

Проблема в функции синхронизации, используемой для анимации. Анимация должна быть такой же быстрой, как перетаскивание пользователем в первую очередь, и быстро замедляться. Следующий код показывает очень простой пример реализации этого поведения.

Во-первых, в моем методе touchesBegan:withEvent: я записал первое местоположение касания в свой буфер точек. Я буферизую два сенсорных местоположения, чтобы получить вектор движения вида, и могут быть разные способы получить вектор.

- (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];
} 

Затем, в методе touchesMoved:withEvent:, я обновил расположение представления. На самом деле, я использовал CALayer, а не представление, так как я хочу использовать пользовательскую функцию синхронизации для его анимации. Итак, я обновляю местоположение слоя в соответствии с пользователем, и для заданного интервала я обновляю буферы местоположения.

#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 - это ссылка на объект CALayer на мой взгляд. Мне не нужна анимация для перетаскивания, поэтому я отключил ее с помощью объекта класса CATransaction.

Наконец, я вычисляю вектор и применяю анимацию в методе touchesEnded:withEvent:. Здесь я создал собственную функцию CAMediaTiming, поэтому она действительно «быстрая, легкая».

#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];

}

Это очень простой пример. Вы можете хотеть лучший механизм получения вектора. Кроме того, это только перемещение визуального компонента (CALayer). Вам, вероятно, понадобится объект UIView для обработки событий из объекта. В этом случае вы можете захотеть анимировать через CALayer и отдельно переместить объект UIView. Может быть несколько способов обработки анимации CALayer и перемещения UIView вместе.

3 голосов
/ 07 марта 2011

Использовать базовую анимацию. Это довольно просто, если вы посмотрите на документы для UIView - создайте анимацию, которая устанавливает конечную позицию представления, и укажите UIViewAnimationOptionCurveEaseOut в качестве временной кривой. Все это займет всего несколько строк для реализации.

0 голосов
/ 06 марта 2011

Вы можете использовать постоянное ускорение для объекта, чтобы остановить движение с постоянной скоростью замедления. Или вы можете увеличить или уменьшить ускорение в зависимости от того, хотите ли вы, чтобы замедление было быстрее / медленнее в направлении достижения нулевой скорости.

struct Velocity {
float x;
float y; }

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

Vector newVelocity = oldVelocity + acceleration * timeDelta;

Vector newPosition = newVelocity * timeDelta;

при условии, что у вас есть нормализованный вектор для вашего направления движения, и вы просто противопоставляете это направление, вы можете использовать float вместо Vector.

Вы должны ограничить новую позицию на границе. И вы должны прекратить итерацию по времени дельта, когда скорость становится отрицательной.

...