Jak poprawnie animować uiscrollview contentOffset

Mam podklasę UIScrollView. Jego zawartość jest wielokrotnego użytku-około 4 lub 5 widoków służy do wyświetlania setek elementów (podczas przewijania ukrytych obiektów ponownie użytych i przeskakuje do innej pozycji, gdy trzeba je zobaczyć) {]}

Co potrzebuję: możliwość automatycznego przewijania widoku przewijania do dowolnej pozycji. Na przykład Mój widok przewijania wyświetla 4, 5 i 6 element, a gdy dotknę jakiegoś przycisku, musi on przewijać do 30 elementu. Innymi słowy, potrzebuję standard zachowanie UIScrollView.

To działa dobrze:

[self setContentOffset:CGPointMake(index*elementWidth, 0) animated:YES];
Ale potrzebuję trochę personalizacji. Na przykład zmień czas trwania animacji, Dodaj kod do wykonania na końcu animacji.

Oczywista decyzja:

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

Ale mam pewne działania związane ze zdarzeniem przewijania, więc teraz wszystkie z nich są w bloku animacji i powoduje to animację wszystkich ramek subview zbyt (dzięki kilku elementów wielokrotnego użytku Wszystkie animuje nie jak ja want)

Pytanie brzmi: Jak zrobić animację niestandardową (w rzeczywistości potrzebuję niestandardowego czasu trwania, akcji na końcu i opcji BeginFromCurrentState) dla offsetu zawartości bez animowania całego kodu, podłączonego do zdarzenia scrollViewDidScroll?

Upd: Dzięki odpowiedź Andrzeja (pierwsza część) rozwiązałem problem z animacją wewnątrz scrollViewDidScroll:

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

Ale scrollViewDidScroll musi (dla moich celów) wykonywać każdą klatkę animacji tak, jak to było w przypadku z

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

Jednak teraz wykonuje tylko raz na początku animacji.

Jak mogę to rozwiązać?
Author: Community, 2014-02-13

2 answers

Czy próbowałeś tego samego podejścia, ale z wyłączoną animacją w scrollViewDidScroll?

Na iOS 7 Możesz spróbować zawijać kod w scrollViewDidScroll w
[UIView performWithoutAnimation:^{
       //Your code here
    }];
W poprzednich wersjach iOS możesz spróbować:]}
  [CATransaction begin];
    [CATransaction setDisableActions:YES];
     //Your code here
    [CATransaction commit];

Update:

Niestety to właśnie tam trafiasz w najtrudniejszą część całej sprawy. setContentOffset: wywołuje delegata tylko raz, jest to odpowiednik setContentOffset:animated:NO, który ponownie wywołuje go tylko raz.

setContentOffset:animated:YES wywołuje delegata, gdy animacja zmienia granice scrollview i chcesz tego, ale nie chcesz dostarczonej animacji, więc jedynym sposobem na obejście tego, co mogę wymyślić, jest stopniowa zmiana contentOffset widoku przewijania, tak aby system animacji nie przeskoczył do ostatecznej wartości, jak to ma miejsce w tej chwili.

Aby to zrobić, możesz spojrzeć na animacje klatek kluczowych, jak w przypadku 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
}];

To da ci dwie aktualizacje i oczywiście możesz użyć trochę matematyki i pętli, aby dodać wiele z nich z odpowiednie terminy.

W poprzednich wersjach iOS, będziesz musiał przejść do CoreAnimation dla animacji klatek kluczowych, ale to w zasadzie to samo z nieco inną składnią.

Metoda 2: Możesz spróbować przepytać warstwę prezentacyjną widoku przewijania na wszelkie zmiany za pomocą timera, który rozpoczynasz na początku animacji, ponieważ niestety właściwości warstwy prezentacyjnej nie są widoczne dla KVO. Lub możesz użyć needsDisplayForKey w podklasie warstwy, aby uzyskać powiadomienie, gdy granice się zmieniają, ale to będzie wymagało trochę pracy, aby skonfigurować i to powoduje przerysowanie, co może mieć wpływ na wydajność.

Metoda 3: Byłoby dokładnie zbadać, co dzieje się z scrollView, gdy animowane jest tak spróbuj przechwycić animację, która zostanie ustawiona na scrollview I zmienić jego parametry, ale ponieważ byłoby to najbardziej hacky, łamliwe ze względu na zmiany Apple i trickiest metoda, nie pójdę do niego.

 17
Author: Andrew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-13 14:45:49

Dobrym sposobem na to jest biblioteka AnimationEngine . Jest to bardzo mała biblioteka: sześć plików, z trzema innymi, jeśli chcesz tłumione zachowanie sprężyny.

Behind the scenes używa CADisplayLink do uruchamiania bloku animacji raz na każdą klatkę. Otrzymujesz prostą w użyciu składnię opartą na blokach oraz kilka funkcji interpolacyjnych i easing , które oszczędzają Twój czas.

Do animacji 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
Author: bcattle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-25 08:20:50