iOS動(dòng)畫指南 - 6.可以很酷的轉(zhuǎn)場(chǎng)動(dòng)畫

在iOS開發(fā)中,界面間的跳轉(zhuǎn)其實(shí)也就是控制器的跳轉(zhuǎn),跳轉(zhuǎn)有很多種,最常用的有push,modal.

  • modal:任何控制器都能通過Modal的形式展?出來.效果:新控制器從屏幕的最底部往上鉆,直到蓋住之前的控制器為?.系統(tǒng)也會(huì)帶有一個(gè)動(dòng)畫.
public func presentViewController(viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?)
public func dismissViewControllerAnimated(flag: Bool, completion: (() -> Void)?)
  • push: 在push中,控制器的管理其實(shí)交給了UINavigationController,所以在push控制器的時(shí)候必須拿到對(duì)應(yīng)的導(dǎo)航控制器. 效果:從右往左出現(xiàn),系統(tǒng)會(huì)帶有一個(gè)默認(rèn)動(dòng)畫.
public func pushViewController(viewController: UIViewController, animated: Bool) 
public func popViewControllerAnimated(animated: Bool) -> UIViewController?

push和model都有系統(tǒng)提供轉(zhuǎn)場(chǎng)動(dòng)畫效果,但有時(shí)系統(tǒng)提供的不一定能滿足開發(fā)需求,這就需要去自定義,說實(shí)話自定義轉(zhuǎn)場(chǎng)動(dòng)畫還是有些麻煩的.但原理其實(shí)很簡(jiǎn)單.

轉(zhuǎn)場(chǎng)動(dòng)畫的原理:

當(dāng)兩個(gè)控制器發(fā)生push.pop或modal.dismiss的時(shí)候,系統(tǒng)會(huì)把原始的控制器放到負(fù)責(zé)轉(zhuǎn)場(chǎng)的控制器容器中,也會(huì)把目標(biāo)控制器放進(jìn)去,但是目標(biāo)控制器是不可見的,因此我們要做的就是把新的控制器顯現(xiàn)出來,把老的控制器移除掉.很簡(jiǎn)單吧!

上面大概介紹了一下控制器的切換及原理,這篇文章我們打算說說自定義轉(zhuǎn)場(chǎng)動(dòng)畫,一個(gè)push一個(gè)modal,雖然自定義modal轉(zhuǎn)場(chǎng)動(dòng)畫用的比較多一點(diǎn),但push也可以了解一下嘛!
先來看下效果,然后準(zhǔn)備上車:


自定義modal轉(zhuǎn)場(chǎng)動(dòng)畫
自定義push轉(zhuǎn)場(chǎng)動(dòng)畫

1.自定義modal轉(zhuǎn)場(chǎng)動(dòng)畫

1.簡(jiǎn)單的modal效果:


很簡(jiǎn)單的modal效果,首先有一個(gè)主控制器,主控制器底部有一個(gè)scrollView,對(duì)scrollView里面圖片添加手勢(shì)監(jiān)聽,并在監(jiān)聽方法里面modal一個(gè)背景圖片一樣的控制器,這樣就可以開始自定義轉(zhuǎn)場(chǎng)動(dòng)畫啦!

2.設(shè)置轉(zhuǎn)場(chǎng)動(dòng)畫的代理:

  1. 新建一個(gè)繼承 NSObject, UIViewControllerAnimatedTransitioning的PopAnimator文件,用于設(shè)置轉(zhuǎn)場(chǎng)動(dòng)畫的代理方法,在里面添加UIViewControllerAnimatedTransitioning協(xié)議必須要實(shí)現(xiàn)的兩個(gè)代理方法:
    // 設(shè)置轉(zhuǎn)場(chǎng)動(dòng)畫持續(xù)時(shí)間
    func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
        return 0
    }
    // 執(zhí)行轉(zhuǎn)場(chǎng)動(dòng)畫
    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {  
    }
  1. 在ViewController(主控制器)中設(shè)置herbDetailsVc(每張圖片對(duì)應(yīng)的控制器)的 transitioningDelegate為自己,為剛剛新建的文件設(shè)置一個(gè)常量 let transition = PopAnimator(),新建一個(gè)extension遵守代理UIViewControllerTransitioningDelegate
extension ViewController: UIViewControllerTransitioningDelegate {

    func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {  
        return transition
    }

    func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return nil
    }
}

如果返回nil,那就使用系統(tǒng)默認(rèn)的轉(zhuǎn)場(chǎng)動(dòng)畫.

3. 創(chuàng)建轉(zhuǎn)場(chǎng)動(dòng)畫.

在PopAnimator的transitionDuration:方法中設(shè)置好時(shí)間.設(shè)定轉(zhuǎn)場(chǎng)動(dòng)畫的內(nèi)容

 func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
       
       // 獲得容器
       let containerView = transitionContext.containerView()!
       // 獲得目標(biāo)view
       // viewForKey 獲取新的和老的控制器的view
       // viewControllerForKey 獲取新的和老的控制器
       let toView = transitionContext.viewForKey(UITransitionContextToViewKey)!
       
       containerView.addSubview(toView)
       toView.alpha = 0.0
       
       UIView.animateWithDuration(duration, animations: { () -> Void in
           toView.alpha = 1.0
           }) { (_) -> Void in
               // 轉(zhuǎn)場(chǎng)動(dòng)畫完成
           transitionContext.completeTransition(true)
       }
   }

內(nèi)容是:通過修改透明度,達(dá)到一個(gè)漸變的效果,如下圖所示.



這樣一個(gè)簡(jiǎn)單的轉(zhuǎn)場(chǎng)效果就實(shí)現(xiàn)啦!是不是很簡(jiǎn)單,只是這還不是我們想要的效果.

4.左上角彈出效果

注意:這一步是在第3步的基礎(chǔ)上修改的.在設(shè)置PopAnimator中添加 var presenting = true.用于判斷到底是彈出控制器,還是后退,因?yàn)榍斑M(jìn)和后退都會(huì)調(diào)用animateTransition這個(gè)代理方法.下一個(gè)實(shí)現(xiàn)效果雖然用不到,但我們先這樣去設(shè)置.
還要設(shè)置一個(gè) var originFrame = CGRect.zero 用于設(shè)置目的控制器的frame,將animateTransition中原來的代碼更改為如下

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
       
       // 獲得容器
       let containerView = transitionContext.containerView()!
       // 獲得目標(biāo)view
       // viewForKey 獲取新的和老的控制器的view
       // viewControllerForKey 獲取新的和老的控制器
       let toView = transitionContext.viewForKey(UITransitionContextToViewKey)!
       let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
       
       // 拿到需要做動(dòng)畫的view
       let herbView = presenting ? toView : fromView
       
       // 獲取初始和最終的frame
       let initialFrame = presenting ? originFrame : herbView.frame
       let finalFrame = presenting ? herbView.frame : originFrame
       
       // 設(shè)置收縮比率
       let xScaleFactor = presenting ? initialFrame.width / finalFrame.width : finalFrame.width / initialFrame.width
       let yScaleFactor = presenting ? initialFrame.height / finalFrame.height : finalFrame.height / initialFrame.height
       let scaleTransform = CGAffineTransformMakeScale(xScaleFactor, yScaleFactor)
       // 當(dāng)presenting的時(shí)候,設(shè)置herbView的初始位置
       if presenting {
           herbView.transform = scaleTransform
           herbView.center = CGPoint(x: CGRectGetMidX(initialFrame), y: CGRectGetMidY(initialFrame))
           herbView.clipsToBounds = true
       }
       
       containerView.addSubview(toView)
       // 保證在最前,不然添加的東西看不到哦
       containerView.bringSubviewToFront(herbView)
       
       // 加了個(gè)彈性效果
       UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 0.4, initialSpringVelocity: 0.0, options: [], animations: { () -> Void in
           
           herbView.transform = self.presenting ? CGAffineTransformIdentity : scaleTransform
           herbView.center = CGPoint(x: CGRectGetMidX(finalFrame), y: CGRectGetMidY(finalFrame))
           
           }) { (_) -> Void in
               transitionContext.completeTransition(true)
       }
   }

效果如下:


仔細(xì)觀察發(fā)現(xiàn)無論是presentViewController還是dismissViewController視圖都是從左上角彈出來的.因?yàn)槲覀儗riginFrame設(shè)置為CGRect.zero了.



5. 最終效果實(shí)現(xiàn).

剛剛我們把originFrame設(shè)置為了CGRect.zero,但我們可以拿到目標(biāo)控制器的原始尺寸啊,這樣就不會(huì)突兀的從左上角彈出來了.

1.在ViewController的extension里面添加修改如下代碼
    func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        
        transition.originFrame = selectedImage!.superview!.convertRect(selectedImage!.frame, toView: nil)
        transition.presenting = true
        selectedImage!.hidden = true
        
        return transition
    }
    func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        
        transition.presenting = false
        return transition
    }
2.當(dāng)dismiss的時(shí)候,剛點(diǎn)擊的圖片會(huì)消失,因?yàn)槲覀冊(cè)趍odal的時(shí)候設(shè)置為了隱藏,所以在dismiss完成后要將selectedImage顯示出來.可以這樣做,在PopAnimator中添加一個(gè)閉包
    var dismissCompletion: (()->())?

然后在animateTransition的UIView.animateWithDuration完成閉包中調(diào)用

UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 1, initialSpringVelocity: 0.0, options: [], animations: { () -> Void in
            
            herbView.transform = self.presenting ? CGAffineTransformIdentity : scaleTransform
            herbView.center = CGPoint(x: CGRectGetMidX(finalFrame), y: CGRectGetMidY(finalFrame))
            
            }) { (_) -> Void in
                if !self.presenting {
                    self.dismissCompletion?()
                }
                transitionContext.completeTransition(true)
        }

最后在ViewController中的viewDidLoad中實(shí)現(xiàn)

 transition.dismissCompletion = {
            self.selectedImage!.hidden = false
        }

3.由于scrollview上的圖片有圓角,所以在轉(zhuǎn)場(chǎng)動(dòng)畫中我們也要實(shí)現(xiàn)圖片圓角與控制器直角的切換.
在PopAnimator中的animateTransition方法中添加如下代碼:

        // 設(shè)置圓角
        let round = CABasicAnimation(keyPath: "cornerRadius")
        round.fromValue = !presenting ? 0.0 : 20.0/xScaleFactor
        round.toValue = presenting ? 0.0 : 20.0/xScaleFactor
        round.duration = duration / 2
        herbView.layer.addAnimation(round, forKey: nil)
        herbView.layer.cornerRadius = presenting ? 0.0 : 20.0/xScaleFactor

最終自定義modal轉(zhuǎn)場(chǎng)效果就完成啦!


自定義modal轉(zhuǎn)場(chǎng)動(dòng)畫

2. 自定義push轉(zhuǎn)場(chǎng)動(dòng)畫

相比自定義modal轉(zhuǎn)場(chǎng)動(dòng)畫,自定義push轉(zhuǎn)場(chǎng)動(dòng)畫的場(chǎng)景不是很多,原理其實(shí)都差不多的.

1. 簡(jiǎn)單的push效果.

push是需要導(dǎo)航控制器的,所以在AppDelegate中加載控制器的時(shí)候,給它套一個(gè)導(dǎo)航控制器.代碼中,ViewController和DetailViewController就是對(duì)應(yīng)切換的兩個(gè)控制器.
給ViewController添加一個(gè)手勢(shì)監(jiān)聽進(jìn)行跳轉(zhuǎn).


2.老樣子,設(shè)置轉(zhuǎn)場(chǎng)動(dòng)畫的代理

  1. 新建一個(gè)繼承 NSObject, UIViewControllerAnimatedTransitioning的RevealAnimator文件,用于設(shè)置轉(zhuǎn)場(chǎng)動(dòng)畫的代理方法,在里面添加UIViewControllerAnimatedTransitioning協(xié)議必須要實(shí)現(xiàn)的兩個(gè)代理方法:
    let animationDuration = 2.0
    // 用于判斷push或者pop
    var operation: UINavigationControllerOperation = .Push

    // 設(shè)置轉(zhuǎn)場(chǎng)動(dòng)畫持續(xù)時(shí)間
    func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
        return 0
    }
    // 執(zhí)行轉(zhuǎn)場(chǎng)動(dòng)畫
    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {  
    }
  1. 在ViewController文件中的viewDidLoad方法中拿到導(dǎo)航控制器將它設(shè)置為代理
       navigationController?.delegate = self

在ViewController文件中添加一個(gè)轉(zhuǎn)場(chǎng)動(dòng)畫控制器容器屬性

    let transition = RevealAnimator()

在ViewController文件中添加一個(gè)extension,實(shí)現(xiàn)代理方法

extension ViewController : UINavigationControllerDelegate { 
    /**
     - parameter navigationController: 拿到設(shè)置代理的導(dǎo)航控制器
     - parameter operation:            .Push .Pop
     - parameter fromVC:               原來的控制器
     - parameter toVC:                 目標(biāo)控制器
     - returns: 返回設(shè)置好的轉(zhuǎn)場(chǎng)動(dòng)畫
     */
    func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        
        transition.operation = operation
        return transition
    }
}

3.添加轉(zhuǎn)場(chǎng)動(dòng)畫

  1. 在RevealAnimator中添加一個(gè)變量保存animateTransition方法的transitionContext,以后會(huì)用到
   weak var storedContext: UIViewControllerContextTransitioning?

老樣子在animateTransition添加一些初始化代碼

            let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) as! ViewController
            
            let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) as! DetailViewController
            
            transitionContext.containerView()?.addSubview(toVC.view)
  1. 添加LOGO的放大動(dòng)畫
    1.新建一個(gè)RWLogoLayer文件,用貝塞爾曲線畫出logo
import UIKit
class RWLogoLayer {
   class func logoLayer() -> CAShapeLayer {
       let layer = CAShapeLayer()
       layer.geometryFlipped = true
       
       let bezier = UIBezierPath()
       bezier.moveToPoint(CGPoint(x: 0.0, y: 0.0))
       bezier.addCurveToPoint(CGPoint(x: 0.0, y: 66.97), controlPoint1:CGPoint(x: 0.0, y: 0.0), controlPoint2:CGPoint(x: 0.0, y: 57.06))
       bezier.addCurveToPoint(CGPoint(x: 16.0, y: 39.0), controlPoint1: CGPoint(x: 27.68, y: 66.97), controlPoint2:CGPoint(x: 42.35, y: 52.75))
       bezier.addCurveToPoint(CGPoint(x: 26.0, y: 17.0), controlPoint1: CGPoint(x: 17.35, y: 35.41), controlPoint2:CGPoint(x: 26, y: 17))
       bezier.addLineToPoint(CGPoint(x: 38.0, y: 34.0))
       bezier.addLineToPoint(CGPoint(x: 49.0, y: 17.0))
       bezier.addLineToPoint(CGPoint(x: 67.0, y: 51.27))
       bezier.addLineToPoint(CGPoint(x: 67.0, y: 0.0))
       bezier.addLineToPoint(CGPoint(x: 0.0, y: 0.0))
       bezier.closePath()
       
       layer.path = bezier.CGPath
       layer.bounds = CGPathGetBoundingBox(layer.path)
       
       return layer
   }
}
  1. 設(shè)置RWLogoLayer的位置尺寸,分別添加到fromVC和toVC上
    在ViewController的viewDidAppear中添加
       logo.position = CGPoint(x: view.layer.bounds.size.width/2,
           y: view.layer.bounds.size.height/2 + 30)
       logo.fillColor = UIColor.whiteColor().CGColor
       view.layer.addSublayer(logo)

在DetailViewController的viewDidLoad中添加

maskLayer.position = CGPoint(x: view.layer.bounds.size.width/2, y: view.layer.bounds.size.height/2)
        view.layer.mask = maskLayer

這邊有個(gè)注意點(diǎn),記得添加

   override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
        
        view.layer.mask = nil
    }

移除mask

  1. 設(shè)置動(dòng)畫,在RevealAnimator的animateTransition方法中添加
            let animation = CABasicAnimation(keyPath: "transform")
            animation.fromValue = NSValue(CATransform3D: CATransform3DIdentity)
            // 添加一個(gè)陰影效果
            animation.toValue = NSValue(CATransform3D:CATransform3DConcat(CATransform3DMakeTranslation(0.0, -10.0, 0.0), CATransform3DMakeScale(150.0, 150.0, 1.0)))
            
            animation.duration = animationDuration
            animation.delegate = self
            animation.fillMode = kCAFillModeForwards
            animation.removedOnCompletion = false
            animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
        
            // 同時(shí)添加到兩個(gè)控制器上
            toVC.maskLayer.addAnimation(animation, forKey: nil)
            fromVC.logo.addAnimation(animation, forKey: nil)
        
            // 給目的控制器設(shè)置一個(gè)漸變效果
            let fadeIn = CABasicAnimation(keyPath: "opacity")
            fadeIn.fromValue = 0.0
            fadeIn.toValue = 1.0
            fadeIn.duration = animationDuration
            toVC.view.layer.addAnimation(fadeIn, forKey: nil)

到這里push的轉(zhuǎn)場(chǎng)效果基本完成了,但會(huì)發(fā)現(xiàn)還有問題,在自定義modal轉(zhuǎn)場(chǎng)動(dòng)畫的時(shí)候,當(dāng)轉(zhuǎn)場(chǎng)動(dòng)畫完成后 需要設(shè)置transitionContext.completeTransition(true),而這邊也是,這樣做是為了在pop的之前把該清理的都清理掉.so重寫animationDidStop

    override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
        if let context = storedContext {   
            context.completeTransition(!context.transitionWasCancelled())
            let fromVc = context.viewControllerForKey(UITransitionContextFromViewControllerKey) as! ViewController
            fromVc.logo.removeAllAnimations()
        }
        storedContext = nil
    }
  1. push已經(jīng)完成啦!現(xiàn)在當(dāng)我們點(diǎn)擊左上角的start按鈕返回時(shí)會(huì)發(fā)生崩潰,是因?yàn)槲覀儧]有在RevealAnimator的animateTransition方法中作判斷,到底是push還是pop.
    定義一個(gè)屬性判斷是pop還是push,將animateTransition中push相關(guān)的代碼放到push判斷語句中去
    var operation: UINavigationControllerOperation = .Push
        if operation == .Push { // push

        }else { // pop

        }
  1. 給pop添加一個(gè)縮小的效果
            let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
            let toView = transitionContext.viewForKey(UITransitionContextToViewKey)!
            
            transitionContext.containerView()?.insertSubview(toView, belowSubview: fromView)
            
            UIView.animateWithDuration(animationDuration, delay: 0.0, options: .CurveEaseIn, animations: {
                fromView.transform = CGAffineTransformMakeScale(0.01, 0.01)
                }, completion: {_ in
                    transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
            })

至此,就完成啦!不過離最終效果還有一定距離!

4.根據(jù)滑動(dòng)手勢(shì)切換控制器

  1. 在ViewController底部添加一個(gè)label,設(shè)置好滑動(dòng)解鎖這幾個(gè)字,這只是提醒,我們的會(huì)為整個(gè)view添加滑動(dòng)手勢(shì)的.
  2. 將ViewController的點(diǎn)擊監(jiān)聽事件改為拖動(dòng)事件.
    let pan = UIPanGestureRecognizer(target: self, action: Selector("didPan:"))
        view.addGestureRecognizer(pan)
   func didPan(recognizer: UIPanGestureRecognizer) {
    }
  1. 根據(jù)滑動(dòng)的偏移量來調(diào)整轉(zhuǎn)場(chǎng)動(dòng)畫的進(jìn)度,也就是說轉(zhuǎn)場(chǎng)動(dòng)畫要是可以交互的.之前所有的轉(zhuǎn)場(chǎng)動(dòng)畫都是開始后,自動(dòng)結(jié)束,不會(huì)隨著人的交互而發(fā)生任何改變.所以原來的方法不能滿足需要.
    這時(shí)需要在ViewController的extension中添加
  // 返回一個(gè)可以交互的轉(zhuǎn)場(chǎng)動(dòng)畫
    func navigationController(navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { 
        if !transition.interactive {
            return nil
        }
        return transition
    }

在RevealAnimator中添加一個(gè)屬性,用于設(shè)置是否可以交互

    // 是否需要交互
    var interactive = false

4.在ViewController的didPan方法中添加手勢(shì)識(shí)別,這邊只處理一部分,其余的傳到RevealAnimator中進(jìn)行

     switch recognizer.state {
        case .Began:
            transition.interactive = true
            navigationController?.pushViewController(DetailViewController(), animated: true)
        default:
            transition.handlePan(recognizer)
        }

5.在RevealAnimator的handlePan方法中,根據(jù)偏移量計(jì)算progress

func handlePan(recognizer: UIPanGestureRecognizer) {
       let translation = recognizer.translationInView(recognizer.view!.superview!)
       var progress: CGFloat = abs(translation.x / 200.0)
       progress = min(max(progress, 0.01), 0.99)
       switch recognizer.state {
       case .Changed:
           // 更新當(dāng)前轉(zhuǎn)場(chǎng)動(dòng)畫播放進(jìn)度
           updateInteractiveTransition(progress)
       case .Cancelled, .Ended:
           if operation == .Push { // push
               let transitionLayer = storedContext!.containerView()!.layer
               transitionLayer.beginTime = CACurrentMediaTime()
               if progress < 0.5 {
                   completionSpeed = -1.0
                   cancelInteractiveTransition() // 停止轉(zhuǎn)場(chǎng)動(dòng)畫,回到from狀態(tài)
               } else {
                   completionSpeed = 1.0
                   finishInteractiveTransition() // 完成轉(zhuǎn)場(chǎng)動(dòng)畫,到to狀態(tài)
               }
           } else { // pop
               if progress < 0.5 {
                   cancelInteractiveTransition()
               } else {
                   finishInteractiveTransition()
               }
           }
           // 使得返回可交互的轉(zhuǎn)場(chǎng)動(dòng)畫為nil,重置動(dòng)畫
           interactive = false
       default:
           break
       }
   }
自定義push轉(zhuǎn)場(chǎng)動(dòng)畫

至此自定義push的轉(zhuǎn)場(chǎng)動(dòng)畫也完成啦!

雖然轉(zhuǎn)場(chǎng)動(dòng)畫不難,但從頭到尾這一整套邏輯,還是有點(diǎn)繁瑣的,可能有的同學(xué)看的有些蒙圈,這是很正常的,因?yàn)橹R(shí)是網(wǎng)狀的啊,線性的邏輯表述并不能表達(dá)清楚網(wǎng)狀知識(shí)的每一個(gè)連接! so,我在下面給出了源碼,給有興趣推敲的同學(xué).

本文整理自 : iOS.Animations.by.Tutorials.v2.0
源碼 : https://github.com/DarielChen/DemoCode
如有疑問,歡迎留言 :-D

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容