it-swarm.com.ru

Как правильно анимировать UIScrollView contentOffset

У меня есть подкласс UIScrollView. Его содержимое можно использовать повторно - для отображения сотен элементов используется около 4 или 5 представлений (при прокрутке скрытых объектов используется повторно и перемещается в другую позицию, когда это необходимо для их просмотра)

Что мне нужно: возможность автоматической прокрутки моего вида прокрутки в любую позицию. Например, мой вид прокрутки отображает 4-й, 5-й и 6-й элемент, и когда я нажимаю на какую-то кнопку, необходимо перейти к 30-му элементу. Другими словами, мне нужно стандартное поведение UIScrollView.

Это отлично работает:

[self setContentOffset:CGPointMake(index*elementWidth, 0) animated:YES];

но мне нужно немного настроек. Например, измените продолжительность анимации, добавьте код для выполнения в конце анимации.

Очевидное решение:

[UIView animateWithDuration:3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
    [self setContentOffset:CGPointMake(index*elementWidth, 0)];
} completion:^(BOOL finished) {
    //some code
}];

но у меня есть некоторые действия, связанные с событием прокрутки, и теперь все они находятся в анимационном блоке, и это также приводит к анимации всех кадров подпредставления (благодаря нескольким элементам многократного использования, все они анимируются не так, как я хочу)

Вопрос заключается в следующем: Как создать пользовательскую анимацию (на самом деле мне нужна настраиваемая продолжительность, действия в конце и опция BeginFromCurrentState) для смещения содержимого БЕЗ анимации всего кода, связанного с событием scrollViewDidScroll?

UPD: Благодаря ответу Эндрю (первая часть) я решил проблему с анимацией внутри scrollViewDidScroll:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    [UIView performWithoutAnimation:^{
        [self refreshTiles];
    }];
}

Но scrollViewDidScroll должен (для моих целей) выполнять каждый кадр анимации, как это было в случае

[self setContentOffset:CGPointMake(index*elementWidth, 0) animated:YES];

Однако теперь он выполняется только один раз при запуске анимации.

Как я могу решить это?

16
Lloyd18

Вы пробовали тот же подход, но с отключенной анимацией в scrollViewDidScroll?

На iOS 7 вы можете попробовать обернуть ваш код в scrollViewDidScroll в

[UIView performWithoutAnimation:^{
       //Your code here
    }];

в предыдущих версиях iOS вы можете попробовать:

  [CATransaction begin];
    [CATransaction setDisableActions:YES];
     //Your code here
    [CATransaction commit];

Обновление:

К сожалению, вот где вы попали в трудную часть всего этого. setContentOffset: вызывает делегата только один раз, это эквивалентно setContentOffset:animated:NO, который снова вызывает его только один раз.

setContentOffset:animated:YES вызывает делегата, так как анимация изменяет границы просмотра прокрутки, и вы этого хотите, но вам не нужна предоставленная анимация, поэтому единственный способ обойти это, что я могу придумать, - это постепенно изменять contentOffset представления прокрутки, так что система анимации не просто переходит к конечному значению, как в данный момент.

Для этого вы можете посмотреть анимацию ключевых кадров, например, для iOS 7:

[UIView animateKeyframesWithDuration:duration delay:delay options:options animations:^{
    [UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.5 animations:^{
       [self setContentOffset:CGPointMake(floorf(index/2) * elementWidth, 0)];
    }];
    [UIView addKeyframeWithRelativeStartTime:0.5 relativeDuration:0.5 animations:^{
        [self setContentOffset:CGPointMake(index*elementWidth, 0)];
    }];
} completion:^(BOOL finished) {
    //Completion Block
}];

Это даст вам два обновления и, конечно, вы можете использовать математические вычисления и цикл, чтобы сложить гораздо больше из них с соответствующими временными интервалами.

В предыдущих версиях iOS вам придется перейти на CoreAnimation для анимации ключевых кадров, но это в основном то же самое с немного другим синтаксисом.

Способ 2: Вы можете попытаться опросить presentationLayer представления прокрутки на предмет любых изменений с помощью таймера, который вы запускаете в начале анимации, поскольку, к сожалению, свойства presentationLayer не наблюдаются в KVO. Или вы можете использовать needsDisplayForKey в подклассе слоя, чтобы получать уведомления при изменении границ, но для этого потребуется некоторая работа по настройке, и это приведет к перерисовке, что может повлиять на производительность.

Метод 3: Было бы точно проанализировать, что происходит с scrollView, когда анимированный - это ДА, попробуйте перехватить анимацию, которая установлена ​​в scrollview, и измените его параметры, но, поскольку это было бы самым взломанным, разрушаемым из-за изменений Apple и самый хитрый метод, я не буду вдаваться в подробности.

18
Andrew

Хороший способ сделать это с помощью библиотеки AnimationEngine . Это очень маленькая библиотека: шесть файлов, и еще три, если вы хотите, чтобы поведение пружины было затухающим.

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

Для анимации contentOffset:

startOffset = scrollView.contentOffset;
endOffset = ..

// Constant speed looks good...
const CGFloat kTimelineAnimationSpeed = 300;
CGFloat timelineAnimationDuration = fabs(deltaToDesiredX) / kTimelineAnimationSpeed;

[INTUAnimationEngine animateWithDuration:timelineAnimationDuration
                                   delay:0
                                  easing:INTULinear
                              animations:^(CGFloat progress) {
                                    self.videoTimelineView.contentOffset = 
                                         INTUInterpolateCGPoint(startOffset, endOffset, progress);
                              }
                             completion:^(BOOL finished) {
                                   autoscrollEnabled = YES;
                             }];
4
bcattle

Попробуй это:

UIView.animate(withDuration: 0.6, animations: {
        self.view.collectionView.contentOffset = newOffset
        self.view.layoutIfNeeded()
    }, completion: nil)
0
gerbil