iOS开发

Youtube pan手势播放视频动画

2022-04-23  本文已影响0人  _浅墨_

效果:

全部代码:

//
//  ViewController.swift
//  catchtrans
//http://camendesign.com/code/video_for_everybody/test.html

import UIKit

import UIKit.UIGestureRecognizerSubclass

import AVFoundation

private enum State {
    
    case closed, open
    
}

extension State {
    
    var opposite: State {
        
        switch self {
            
        case .open:
            
            return .closed
            
        case .closed:
            
            return .open
            
        }
        
    }
    
}

class ViewController: UIViewController {
    
    private var animationProgress: CGFloat = 0
    
    var transitionAnimator = UIViewPropertyAnimator()
    
    private var currentState: State = .closed
    
    private lazy var panRecognizer: InstantPanGestureRecognizer = {
        
        let recognizer = InstantPanGestureRecognizer()
        
        recognizer.addTarget(self, action: #selector(popupViewPanned(recognizer:)))
        
        return recognizer
        
    }()
    
    lazy var thumbnailImageView: UIImageView = {
        
        return UIImageView()
        
    }()
    
    lazy var popupView: UIView = {
        
       let _popupView = UIView()
        
        _popupView.backgroundColor = UIColor.gray
        
        return _popupView
        
    }()
    
    var bottomConstraint = NSLayoutConstraint()
    
    var popupOffset: CGFloat = 0
    
    var viewHeight: CGFloat = 0
    
    lazy var videoView: UIView = {
        
       return UIView()
        
    }()
    
    let videoURLString: String = Bundle.main.path(forResource: "clip", ofType: "mp4")!
    
    var url: URL {
        
        return URL(fileURLWithPath: videoURLString)
        
    }
    
    lazy var asset: AVURLAsset = {
        
        var asset: AVURLAsset = AVURLAsset(url: url)
        
        return asset
        
    }()
    
    lazy var playerItem: AVPlayerItem = {
        
        var playerItem: AVPlayerItem = AVPlayerItem(asset: self.asset)
        
        return playerItem
        
    }()
    
    lazy var player: AVPlayer = {
        
        var player: AVPlayer = AVPlayer(playerItem: self.playerItem)
        
        player.actionAtItemEnd = .none
        
        return player
        
    }()
    
    lazy var playerLayer: AVPlayerLayer = {
        
        var playerLayer: AVPlayerLayer = AVPlayerLayer(player: self.player)
        
        return playerLayer
        
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        viewHeight = view.frame.size.height
        
        popupOffset = viewHeight - CGFloat(60)
        
        layout()
        
        popupView.addGestureRecognizer(panRecognizer)
        
    }

    func layout() {
        
        popupView.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(popupView)
        
        popupView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        
        popupView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        
        bottomConstraint = popupView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: popupOffset)
        
        bottomConstraint.isActive = true
        
        popupView.heightAnchor.constraint(equalToConstant: viewHeight).isActive = true
        
        popupView.addSubview(videoView)
        
        videoView.translatesAutoresizingMaskIntoConstraints = false
        
        videoView.backgroundColor = UIColor.white
        
        videoView.leadingAnchor.constraint(equalTo: popupView.leadingAnchor, constant: 30).isActive = true
        
        videoView.trailingAnchor.constraint(equalTo: popupView.trailingAnchor, constant: -30).isActive = true
        
        videoView.topAnchor.constraint(equalTo: popupView.topAnchor, constant: 60).isActive = true
        
        NSLayoutConstraint(item: videoView, attribute: .height, relatedBy: .equal, toItem: videoView, attribute: .width, multiplier: 1, constant: 0).isActive = true
        
        popupView.addSubview(thumbnailImageView)
        
        thumbnailImageView.translatesAutoresizingMaskIntoConstraints = false
        
        thumbnailImageView.backgroundColor = UIColor.white
        
        thumbnailImageView.leadingAnchor.constraint(equalTo: popupView.leadingAnchor, constant: 15).isActive = true
        
        thumbnailImageView.topAnchor.constraint(equalTo: popupView.topAnchor, constant: 10).isActive = true
        
        thumbnailImageView.heightAnchor.constraint(equalToConstant: 40).isActive = true
        
        thumbnailImageView.widthAnchor.constraint(equalToConstant: 40).isActive = true
        
        thumbnailImageView.contentMode = .scaleAspectFit
        
        videoView.layer.insertSublayer(playerLayer, at: 0)
        
        thumbnailImageView.image = getThumbnailImage()
        
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        
        playerLayer.frame = videoView.bounds
        
    }
    
    @objc func popupViewPanned(recognizer: UIPanGestureRecognizer) {
        
        switch recognizer.state {
            
        case .began:
            
            if player.isPlaying {
                
                player.pause()
                
            }
            
            thumbnailImageView.alpha = 0
            
            animateTransition(to: currentState.opposite, duration: 2)
            
            transitionAnimator.pauseAnimation()
            
            animationProgress = transitionAnimator.fractionComplete
            
        case .changed:
            
            let translation = recognizer.translation(in: popupView)
            
            var fraction = -translation.y / popupOffset
            
            if currentState == .open { fraction *= -1 }
            
            if transitionAnimator.isReversed { fraction *= -1  }
            
            transitionAnimator.fractionComplete = fraction + animationProgress
                
        case .ended:
            
            let yVelocity = recognizer.velocity(in: popupView).y
            
            let shouldClose = yVelocity > 0
            
            if yVelocity == 0 {
                
                transitionAnimator.continueAnimation(withTimingParameters: nil, durationFactor: 0)
                
                break
                
            }
            
            switch currentState {
                
            case .open:
                
                if !shouldClose && !transitionAnimator.isReversed { transitionAnimator.isReversed = !transitionAnimator.isReversed }
                
                if shouldClose && transitionAnimator.isReversed { transitionAnimator.isReversed = !transitionAnimator.isReversed }
                
            case .closed:
                
                if shouldClose && !transitionAnimator.isReversed { transitionAnimator.isReversed = !transitionAnimator.isReversed }
                
                if !shouldClose && transitionAnimator.isReversed { transitionAnimator.isReversed = !transitionAnimator.isReversed }
                
            }
            
            transitionAnimator.continueAnimation(withTimingParameters: nil, durationFactor: 0)
            
        default:
            
            ()
            
        }
        
    }
    
    private func animateTransition(to state: State, duration: TimeInterval) {
        
        if transitionAnimator.isRunning { return }
        
        transitionAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: 1.0, animations: {
            
            switch state {
                
            case .open:
                
                self.bottomConstraint.constant = 0
                
            case .closed:
                
                self.bottomConstraint.constant = self.popupOffset
                
            }
            
            self.view.layoutIfNeeded()
            
        })
        
        transitionAnimator.addCompletion { (position) in
            
            switch position {
                
            case .start:
                
                self.currentState = state.opposite
                
            case .end:
                
                self.currentState = state
                
            case .current:
                
                ()
                
            @unknown default:
                
                break
                
            }
            
            switch self.currentState {
                
            case .open:
                
                self.bottomConstraint.constant = 0
                
                self.player.play()
                
            case .closed:
                
                self.bottomConstraint.constant = self.popupOffset
                
                self.thumbnailImageView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
                
                UIView.animate(withDuration: 0.2, animations: {
                    
                    self.thumbnailImageView.transform = CGAffineTransform.identity
                    
                    self.thumbnailImageView.alpha = 1.0
                    
                })
                
                
                
            }
            
        }
        
        transitionAnimator.startAnimation()
        
    }
    
    func getThumbnailImage() -> UIImage? {
        
        let assetImgGenerate = AVAssetImageGenerator(asset: asset)
        
        assetImgGenerate.appliesPreferredTrackTransform = true
        
        let time = CMTimeMakeWithSeconds(Float64(5), preferredTimescale: 600)
        
        do {
            
            let img = try assetImgGenerate.copyCGImage(at: time, actualTime: nil)
            
            let thumbnail = UIImage(cgImage: img)
            
            return thumbnail
            
        } catch {
            
            return nil
            
        }
        
    }

}

extension AVPlayer {
    
    var isPlaying: Bool {
        
        return (rate != 0 && (error == nil))
        
    }
    
}

class InstantPanGestureRecognizer: UIPanGestureRecognizer {
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        
        if (self.state == UIGestureRecognizer.State.began) { return }
        
        super.touchesBegan(touches, with: event)
        
        self.state = UIGestureRecognizer.State.began
        
    }
    
}


源码:https://github.com/MFiOSDemos/YoutubePanVideo

上一篇下一篇

猜你喜欢

热点阅读