iOS(Swift) AnimationController

232 阅读2分钟

UIView的动画,没法半路取消,逆向,有时候确实有这样的需求.

来源

写了个swift版本的AnimationController,用于控制动画前进后退暂停.

录屏2023-12-19 17.30.09.gif

//
//  AnimationController.swift
//  SwiftuiCli
//
//  Created by hccc on 2023/12/11.
//

import Foundation
import RxCocoa
import RxSwift

private func clampValue(x: CGFloat, min: CGFloat, max: CGFloat) -> CGFloat {
    //  assert(min <= max && !max.isNaN && !min.isNaN);
    if x < min {
        return min
    }
    if x > max {
        return max
    }
    return x
}

class AnimationController: NSObject {
    enum AnimationDirection {
        case forward
        case reverse
    }

    enum AnimationStatus {
        case dismissed
        case forward
        case reverse
        case completed
    }
    
    struct AnimationProgress {
        // 0~1进度
        var progress: CGFloat
        // 当前进度时间
        var currentDuration: CGFloat
        // 当前状态
        var status: AnimationStatus
        // 当前值
        var value: CGFloat
    }
    
    static let kDefault: Int = 1000 / 25
    
    /// 毫秒step值
    var step: Int = kDefault
    /// 毫秒结算
    var duration: CGFloat = 1000
    var timer: Disposable?
    var value: CGFloat = 0
    var lowerBound: CGFloat = 0
    var upperBound: CGFloat = 1
    var curve: CurveProtocol = Curves.linear
    
    var processListener = PublishSubject<AnimationProgress>()
    
    private var direction: AnimationDirection = .forward
    private var progress: CGFloat = 0
    private var status: AnimationStatus = .dismissed
    
    let disposeBag = DisposeBag()
    
    var isAniamting: Bool { timer != nil }
    
    init(duration: CGFloat,
         lowerBound: CGFloat = 0,
         upperBound: CGFloat = 1,
         curve: CurveProtocol = Curves.linear)
    {
        super.init()
        self.duration = duration
        self.lowerBound = lowerBound
        self.upperBound = upperBound
        self.curve = curve
    }
    
    func forward() {
        direction = .forward
        _startTimer()
    }
    
    func reverser() {
        direction = .reverse
        _startTimer()
    }
    
    func pause() {
        stop()
    }
    
    func resume() {
        _startTimer()
    }
    
    private func _startTimer() {
        stop()
        print(Date().timeIntervalSince1970)
        timer = Observable<Int>
            .interval(.milliseconds(step), scheduler: MainScheduler.asyncInstance)
            .take(until: rx.deallocated)
            .subscribe(onNext: { [weak self] _ in
                self?._doStep()
            })
    }
    
    private func _doStep() {
        switch direction {
        case .forward:
            progress = progress + (CGFloat(step) / duration)
        case .reverse:
            progress = progress - (CGFloat(step) / duration)
        }
        // 1进度
        progress = clampValue(x: progress, min: 0, max: 1)
       
        value = curve.transform(startValue: lowerBound, endValue: upperBound, time: progress)
        // 2当前时间
//        value = InterpolationDuration(
//            begin: lowerBound,
//            end: upperBound
//        )
//        .clamp(process: progress)
        // 3状态
        let status: AnimationStatus
        if value == lowerBound {
            status = .dismissed
        } else if value == upperBound {
            status = .completed
        } else {
            if direction == .forward {
                status = .forward
            } else {
                status = .reverse
            }
        }
//        print(progress, value)
        // 进度
        processListener.onNext(
            AnimationProgress(
                progress: progress,
                currentDuration: progress * duration,
                status: status,
                value: value
            )
        )
        if value >= upperBound || value <= lowerBound {
            stop()
            print(Date().timeIntervalSince1970)
        }
    }
    
    func stop() {
        if isAniamting {
            timer?.dispose()
            timer = nil
        }
    }
}


protocol CurveProtocol {
    func transform(startValue: CGFloat, endValue: CGFloat, time: CGFloat) -> CGFloat
}

enum Curves: CurveProtocol {
    case linear
    case easyIn
    case easyOut
    case easyInOut
    
    func transform(startValue: CGFloat, endValue: CGFloat, time: CGFloat) -> CGFloat {
        switch self {
        case .linear:
            return startValue + (endValue - startValue) * time
        case .easyIn:
            return startValue + (endValue - startValue) * pow(time, 3)
        case .easyOut:
            let t = 1 - time
            return startValue + (endValue - startValue) * (1 - pow(t, 3))
        case .easyInOut:
            return startValue + (endValue - startValue) * (0.5 - 0.5 * cos(time * .pi))
        }
    }
}

前段时间写的,代码也是十分的浅显易懂,当然也有一定的优化空间,比如timer的执行队列等等,有需要的人自取即可.

PS

这种什么都不用说明,直接水一篇文章的感觉真爽.

结尾

后面开始不会太过频繁更新,准备写一个flutter工具集,做个自己的模板项目,收拾心情准备找工作.