Animator クラスを使用して、Android アプリでかなり複雑なアニメーションを作成しました。このアニメーションをiOSに移植したいです。できれば、Android Animator に多少似ています。私は周りを見回しましたが、私が欲しいものは何もないようです。私が得た最も近いものはCAAnimationでした。しかし残念ながら、グループに入れられた場合、すべての子デリゲートは無視されます。
Androidで作成したアニメーションから始めましょう。3 つのビュー グループ (ImageView と TextView を含む) をアニメーション化しています。ボタンごとに、ビューを左に移動し、同時にアルファを 0 にアニメーション化するアニメーションがあります。そのアニメーションの後に、同じビューを右から元の位置に移動し、アルファを 1 にアニメーション化する別のアニメーションがあります。移動アニメーションとアルファ アニメーションの他に、スケール アニメーションもあるビューが 1 つあります。すべてのビューが異なるタイミング関数 (イージング) を使用しています。アニメーション インとアニメーション アウトは異なり、1 つのビューにはスケール用の異なるタイミング関数がありますが、アルファ アニメーションと変換アニメーションは同じものを使用します。最初のアニメーションが終了したら、2 番目のアニメーションを準備するための値を設定しています。スケール アニメーションの継続時間も、移動およびアルファ アニメーションよりも短くなります。AnimatorSet (基本的にはアニメーションのグループ) 内に単一のアニメーション (変換とアルファ) を入れています。この AnimatorSet は別の AnimatorSet に配置され、アニメーションを交互に実行します (最初にアニメーション化してから)。そして、この AnimatorSet は、3 つのボタンすべてのアニメーションを同時に実行する別の AnimatorSet に配置されます。
長い説明で申し訳ありません。しかし、このようにして、私がこれを iOS に移植しようとしている方法を理解できます。これは UIView.animate() には複雑すぎます。CAAnimationGroup に配置されている場合、CAAnimation はデリゲートをオーバーライドします。ViewPropertyAnimator では、私の知る限り、カスタム タイミング関数を使用できず、複数のアニメーションを調整できません。
私がこれに何を使うことができるか考えている人はいますか?また、アニメーションのティックごとにコールバックを提供するカスタム実装でも問題ないため、それに応じてビューを更新できます。
編集
Android アニメーション コード:
fun setState(newState: State) {
if(state == newState) {
return
}
processing = false
val prevState = state
state = newState
val reversed = newState.ordinal < prevState.ordinal
val animators = ArrayList<Animator>()
animators.add(getMiddleButtonAnimator(reversed, halfAnimationDone = {
displayMiddleButtonState()
}))
if(prevState == State.TAKE_PICTURE || newState == State.TAKE_PICTURE) {
animators.add(getButtonAnimator(leftButton, leftButton, leftButton.imageView.width.toFloat(), reversed, halfAnimationDone = {
displayLeftButtonState()
}))
}
if(prevState == State.TAKE_PICTURE || newState == State.TAKE_PICTURE) {
animators.add(getButtonAnimator(
if(newState == State.TAKE_PICTURE) rightButton else null,
if(newState == State.CROP_PICTURE) rightButton else null,
rightButton.imageView.width.toFloat(),
reversed,
halfAnimationDone = {
displayRightButtonState(inAnimation = true)
}))
}
val animatorSet = AnimatorSet()
animatorSet.playTogether(animators)
animatorSet.start()
}
fun getButtonAnimator(animateInView: View?, animateOutView: View?, maxTranslationXValue: Float, reversed: Boolean, halfAnimationDone: () -> Unit): Animator {
val animators = ArrayList<Animator>()
if(animateInView != null) {
val animateInAnimator = getSingleButtonAnimator(animateInView, maxTranslationXValue, true, reversed)
if(animateOutView == null) {
animateInAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
halfAnimationDone()
}
})
}
animators.add(animateInAnimator)
}
if(animateOutView != null) {
val animateOutAnimator = getSingleButtonAnimator(animateOutView, maxTranslationXValue, false, reversed)
animateOutAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
halfAnimationDone()
}
})
animators.add(animateOutAnimator)
}
val animatorSet = AnimatorSet()
animatorSet.playTogether(animators)
return animatorSet
}
private fun getSingleButtonAnimator(animateView: View, maxTranslationXValue: Float, animateIn: Boolean, reversed: Boolean): Animator {
val translateDuration = 140L
val fadeDuration = translateDuration
val translateValues =
if(animateIn) {
if(reversed) floatArrayOf(-maxTranslationXValue, 0f)
else floatArrayOf(maxTranslationXValue, 0f)
} else {
if(reversed) floatArrayOf(0f, maxTranslationXValue)
else floatArrayOf(0f, -maxTranslationXValue)
}
val alphaValues =
if(animateIn) {
floatArrayOf(0f, 1f)
} else {
floatArrayOf(1f, 0f)
}
val translateAnimator = ObjectAnimator.ofFloat(animateView, "translationX", *translateValues)
val fadeAnimator = ObjectAnimator.ofFloat(animateView, "alpha", *alphaValues)
translateAnimator.duration = translateDuration
fadeAnimator.duration = fadeDuration
if(animateIn) {
translateAnimator.interpolator = EasingInterpolator(Ease.CUBIC_OUT)
fadeAnimator.interpolator = EasingInterpolator(Ease.CUBIC_OUT)
} else {
translateAnimator.interpolator = EasingInterpolator(Ease.CUBIC_IN)
fadeAnimator.interpolator = EasingInterpolator(Ease.CUBIC_IN)
}
val animateSet = AnimatorSet()
if(animateIn) {
animateSet.startDelay = translateDuration
}
animateSet.playTogether(translateAnimator, fadeAnimator)
return animateSet
}
fun getMiddleButtonAnimator(reversed: Boolean, halfAnimationDone: () -> Unit): Animator {
val animateInAnimator = getMiddleButtonSingleAnimator(true, reversed)
val animateOutAnimator = getMiddleButtonSingleAnimator(false, reversed)
animateOutAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
halfAnimationDone()
}
})
val animatorSet = AnimatorSet()
animatorSet.playTogether(animateInAnimator, animateOutAnimator)
return animatorSet
}
private fun getMiddleButtonSingleAnimator(animateIn: Boolean, reversed: Boolean): Animator {
val translateDuration = 140L
val scaleDuration = 100L
val fadeDuration = translateDuration
val maxTranslationXValue = middleButtonImageView.width.toFloat()
val translateValues =
if(animateIn) {
if(reversed) floatArrayOf(-maxTranslationXValue, 0f)
else floatArrayOf(maxTranslationXValue, 0f)
} else {
if(reversed) floatArrayOf(0f, maxTranslationXValue)
else floatArrayOf(0f, -maxTranslationXValue)
}
val scaleValues =
if(animateIn) floatArrayOf(0.8f, 1f)
else floatArrayOf(1f, 0.8f)
val alphaValues =
if(animateIn) {
floatArrayOf(0f, 1f)
} else {
floatArrayOf(1f, 0f)
}
val translateAnimator = ObjectAnimator.ofFloat(middleButtonImageView, "translationX", *translateValues)
val scaleXAnimator = ObjectAnimator.ofFloat(middleButtonImageView, "scaleX", *scaleValues)
val scaleYAnimator = ObjectAnimator.ofFloat(middleButtonImageView, "scaleY", *scaleValues)
val fadeAnimator = ObjectAnimator.ofFloat(middleButtonImageView, "alpha", *alphaValues)
translateAnimator.duration = translateDuration
scaleXAnimator.duration = scaleDuration
scaleYAnimator.duration = scaleDuration
fadeAnimator.duration = fadeDuration
if(animateIn) {
translateAnimator.interpolator = EasingInterpolator(Ease.QUINT_OUT)
scaleXAnimator.interpolator = EasingInterpolator(Ease.CIRC_OUT)
scaleYAnimator.interpolator = EasingInterpolator(Ease.CIRC_OUT)
fadeAnimator.interpolator = EasingInterpolator(Ease.QUINT_OUT)
} else {
translateAnimator.interpolator = EasingInterpolator(Ease.QUINT_IN)
scaleXAnimator.interpolator = EasingInterpolator(Ease.CIRC_IN)
scaleYAnimator.interpolator = EasingInterpolator(Ease.CIRC_IN)
fadeAnimator.interpolator = EasingInterpolator(Ease.QUINT_IN)
}
if(animateIn) {
val scaleStartDelay = translateDuration - scaleDuration
val scaleStartValue = scaleValues[0]
middleButtonImageView.scaleX = scaleStartValue
middleButtonImageView.scaleY = scaleStartValue
scaleXAnimator.startDelay = scaleStartDelay
scaleYAnimator.startDelay = scaleStartDelay
}
val animateSet = AnimatorSet()
if(animateIn) {
animateSet.startDelay = translateDuration
}
animateSet.playTogether(translateAnimator, scaleXAnimator, scaleYAnimator)
return animateSet
}
編集 2
Android でアニメーションがどのように表示されるかを示すビデオを次に示します。