1

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 でアニメーションがどのように表示されるかを示すビデオを次に示します。

https://youtu.be/IKAB9A9qHic

4

2 に答える 2

1

これが、あなたが探していると思うアニメーションの始まりです。スライドのタイミングが気に入らない場合は、各フレームをより細かく制御できるように を切り替えることができUIView.animateます.curveEaseInOut。アニメートしているビューごとに が必要ですCAKeyframeAnimationCAKeyFrameAnimation

ここに画像の説明を入力

これはプレイグラウンドであり、空のプレイグラウンドにコピー アンド ペーストして動作を確認できます。

import UIKit
import Foundation
import PlaygroundSupport

class ViewController: UIViewController {

    let bottomBar = UIView()
    let orangeButton = UIButton(frame: CGRect(x: 0, y: 10, width: 75, height: 75))
    let yellow = UIView(frame: CGRect(x: 20, y: 20, width: 35, height: 35))
    let magenta = UIView(frame: CGRect(x: 80, y: 30, width: 15, height: 15))
    let cyan = UIView(frame: CGRect(x: 50, y: 20, width: 35, height: 35))
    let brown = UIView(frame: CGRect(x: 150, y: 30, width:
    15, height: 15))
    let leftBox = UIView(frame: CGRect(x: 15, y: 10, width: 125, height: 75))

    func setup() {

        let reset = UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
        reset.backgroundColor = .white
        reset.addTarget(self, action: #selector(resetAnimation), for: .touchUpInside)
        self.view.addSubview(reset)

        bottomBar.frame = CGRect(x: 0, y: self.view.frame.size.height - 100, width: self.view.frame.size.width, height: 100)
        bottomBar.backgroundColor = .purple
        self.view.addSubview(bottomBar)

        orangeButton.backgroundColor = .orange
        orangeButton.center.x = bottomBar.frame.size.width / 2
        orangeButton.addTarget(self, action: #selector(orangeTapped(sender:)), for: .touchUpInside)
        orangeButton.clipsToBounds = true
        bottomBar.addSubview(orangeButton)

        yellow.backgroundColor = .yellow
        orangeButton.addSubview(yellow)

        magenta.backgroundColor = .magenta
        magenta.alpha = 0
        orangeButton.addSubview(magenta)

        // Left box is an invisible bounding box to get the effect that the view appeared from nowhere
        // Clips to bounds so you cannot see the view when it has not been animated
        // Try setting to false
        leftBox.clipsToBounds = true
        bottomBar.addSubview(leftBox)

        cyan.backgroundColor = .cyan
        leftBox.addSubview(cyan)

        brown.backgroundColor = .brown
        brown.alpha = 0
        leftBox.addSubview(brown)
    }

    @objc func orangeTapped(sender: UIButton) {

        // Perform animation
        UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseInOut, animations: {

            self.yellow.frame = CGRect(x: -20, y: 30, width: 15, height: 15)
            self.yellow.alpha = 0

            self.magenta.frame = CGRect(x: 20, y: 20, width: 35, height: 35)
            self.magenta.alpha = 1

            self.cyan.frame = CGRect(x: -150, y: 30, width: 15, height: 15)
            self.cyan.alpha = 0

            self.brown.frame = CGRect(x: 50, y: 20, width: 35, height: 35)
            self.brown.alpha = 1

        }, completion: nil)
    }

    @objc func resetAnimation() {
        // Reset the animation back to the start
        yellow.frame = CGRect(x: 20, y: 20, width: 35, height: 35)
        yellow.alpha = 1
        magenta.frame = CGRect(x: 80, y: 30, width: 15, height: 15)
        magenta.alpha = 0
        cyan.frame = CGRect(x: 50, y: 20, width: 35, height: 35)
        cyan.alpha = 1
        brown.frame = CGRect(x: 150, y: 30, width: 15, height: 15)
        brown.alpha = 0
    }

}
let viewController = ViewController()
viewController.view.frame = CGRect(x: 0, y: 0, width: 375, height: 667)
viewController.view.backgroundColor = .blue
viewController.setup()
PlaygroundPage.current.liveView = viewController
于 2018-06-07T13:59:58.370 に答える