web-dev-qa-db-ja.com

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

しかし、スクロールイベントに接続されているいくつかのアクションがあるため、それらのすべてがアニメーションブロックにあり、すべてのサブビューのフレームもアニメーション化されます(いくつかの再利用可能な要素のおかげで、すべてのアニメーションが希望どおりにアニメーション化されません)

質問は次のとおりです:コンテンツオフセット[〜#〜]なし[〜#〜]scrollViewDidScrollイベントに接続されたすべてのコードをアニメーション化しますか?

UPD:おかげで Andrewの回答 (前半)scrollViewDidScroll内のアニメーションに関する問題を解決しました:

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

しかし、scrollViewDidScrollは(私の目的では)アニメーションのすべてのフレームを実行する必要があります。

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

ただし、アニメーションの開始時に一度だけ実行されるようになりました。

どうすればこれを解決できますか?

18
Lloyd18

同じアプローチを試しましたが、scrollViewDidScrollのアニメーションが無効になっていますか?

IOS 7では、scrollViewDidScrollでコードをラップしてみてください

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

以前のiOSバージョンでは、以下を試すことができます。

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

更新:

残念ながら、それはあなたが全体の難しい部分にぶつかるところです。 setContentOffset:はデリゲートを1回だけ呼び出します。これはsetContentOffset:animated:NOと同じで、もう一度1回だけ呼び出します。

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

これにより、2つの更新が得られます。もちろん、いくつかの計算とループを使用して、これらの多くを適切なタイミングで加算することができます。

以前のiOSバージョンでは、キーフレームアニメーションの場合はCoreAnimationにドロップする必要がありますが、それは基本的に同じで、構文は少し異なります。

方法2:残念ながら、PresentationLayerのプロパティはKVOで監視できないため、アニメーションの最初から開始するタイマーを使用して、スクロールビューのpresentationLayerの変更をポーリングしてみてください。または、レイヤーのサブクラスでneedsDisplayForKeyを使用して、境界が変更されたときに通知を受け取ることもできますが、そのためにはいくつかの設定が必要であり、再描画が発生するため、パフォーマンスに影響する可能性があります。

方法3:アニメーションがYESの場合にscrollViewに何が起こるかを正確に分析することは、scrollviewに設定されるアニメーションをインターセプトしてパラメーターを変更することですが、これはAppleの変更とトリッキーな方法により最もハッキングされ、壊れやすいため、私はそれには入りません。

18
Andrew

これを行うための良い方法は、 AnimationEngine ライブラリを使用することです。これは非常に小さなライブラリです。6つのファイルがあり、減衰されたばねの動作が必要な場合はさらに3つあります。

舞台裏ではCADisplayLinkを使用して、フレームごとに1回アニメーションブロックを実行します。使いやすいクリーンなブロックベースの構文と、時間を節約する 補間イージング関数 の束が得られます。

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;
                             }];
5
bcattle

これを試して:

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