仿iOS新版微信朋友圈浏览界面缩小浮窗功能,自

作者:澳门新葡京平台游戏

2.自定义转场动画(支持屏幕左边缘pop),来做到手势pop时用户可选是否保留操作。本文不做自定义转场的教学,但我会贴上代码。留一个专场动画详解

pop转场动画

1、pop转场动画和push转场动画类似,在类HSPopAnimation中实现UIViewControllerAnimatedTransitioning协议。

@implementation HSPopAnimation

- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext{
    return 0.5;
}

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{

    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];

    UIView* toView = nil;
    UIView* fromView = nil;

    if ([transitionContext respondsToSelector:@selector(viewForKey:)]) {
        fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
        toView = [transitionContext viewForKey:UITransitionContextToViewKey];
    } else {
        fromView = fromViewController.view;
        toView = toViewController.view;
    }

    //将toView加到fromView的下面,非常重要!!!
    [[transitionContext containerView] insertSubview:toView belowSubview:fromView];

    CGFloat width = [UIScreen mainScreen].bounds.size.width;
    CGFloat height = [UIScreen mainScreen].bounds.size.height;

    fromView.frame = CGRectMake(0, 0, width, height);
    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        fromView.frame = CGRectMake(width, 0, width, height);
    } completion:^(BOOL finished) {

        [transitionContext completeTransition:!transitionContext.transitionWasCancelled];
    }];
}

@end

这里pop动画基本和push动画是相反的过程,当然你也可以指定别的方式的动画。
这里from、to和push动画里面的from、to值已经互换了,所以如果将push和pop动画写在一起的话,要特别注意,不过建议将push和pop动画分别定义到不同的类中,方便管理。
2、在SecondViewControlle类中设置导航控制器的Delegate,并实现以下协议:

- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{
    if (operation == UINavigationControllerOperationPop) {
        return [[HSPopAnimation alloc] init];
    }
    return nil;
}

pop转场动画就完成了,效果图如下:

新葡京8455 1

pop转场动画

1、初始化得到动画对象

let animator = LVAnimator()

这个对象相当于动画管理控制器,接收push和present的事件,可根据fromVC和toVC来分配对应的转场动画

有两种类型嘛,一种是navigationController自带的pushpop,就是由导航控制器来控制VC的切换,还一种是presentdismiss,在两个VC之间直接切换。对这两种类型,系统都有默认的交互动画,但有时你可能想自定义交互的方式,比如像iOS自带相册里点击图片看大图时,新的界面是从中间放大来展现出来的。

新葡京8455 2如图功能

主要涉及的API

1、UIViewControllerAnimatedTransitioning:转场动画协议,实现此协议定义转场的动画行为。

// 定义转场动画的时间
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;

// 定义转场动画的行为
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;

2、 UIViewControllerContextTransitioning:转场动画上下文,这个协议定义了转场动画具体参数,控制转场动画的状态,这个协议一般由系统实现,在转场发生时提供给我们使用。

FromTo:转场是两个视图控制器(ViewController)的行为,由一个视图控制器切换到另一个视图控制器,原先呈现的视图控制器叫FromViewController,将要呈现的视图控制器叫ToViewController,那么FromViewController的view叫做FromView,ToViewController的view叫做ToView

对应push和pop来说是两个不同的转场,它们的From和To在两个转场中使相互调换的。

新葡京8455 3

控制器A push到 控制器B,那么From是A, To是B

新葡京8455 4

控制器B pop到 控制器A, 那么From是B, To是A

containerView:转场动画完成都是在containerView里面。

3、UIViewControllerInteractiveTransitioning:转场的交互协议,用来控制转场动画的状态或进度。

//设置转场进度, 取值范围 [0..1]
- (void)updateInteractiveTransition:(CGFloat)percentComplete;
//完成转场,呈现to
- (void)finishInteractiveTransition;
//取消转场,呈现from
- (void)cancelInteractiveTransition;

4、UIPercentDrivenInteractiveTransition:官方提供的实现UIViewControllerInteractiveTransitioning协议的类,可以直接使用。

上面简单的介绍了转场动画涉及的API,这一节主要通过导航控制器的push和pop转场动画来介绍这些自定义转场动画的流程。

4、present动画需特别处理

//present的vclet vc = LVMineVC()//present转场比较特殊,需将跳转的vc代理指向当前动画对象animator.registerDelegatepresent(vc, animated: true)

起初是从push和pop动画开始封装的,包括手势控制动画一切顺利,但是加入present和dismiss后一切就不对了... present转场需要把目标vc的transitioningDelegate对象指向当前对象,所以就有了以下代码

let vc = LVMineVC()animator.registerDelegatepresent(vc, animated: true)

另外一个问题是封装present和dismiss后,手势驱动出了问题,push和pop手势动画时,松手动画会平滑过渡结束,而present和dismiss则是一闪而过直接跳到结束状态,没有中间平滑过渡的动画了...这怎么办...拆开分为两套方法不是最初的意愿,就一个字... 正面刚!

最后网上也看了一些资料,用了CADisplayLink解决了

func startLink() { if link == nil { link = CADisplayLink(target: self, selector: #selector(LVTransitioningDelegateHelper.linkUpdate)) link?.add(to: RunLoop.current, forMode: .commonModes) }}func stopLink() { link?.invalidate() link = nil}@objc func linkUpdate() { progress  = rate if progress >= 0.98 { stopLink() interactive?.finish() interactive = nil } else { interactive?.update }}

原理就是当手势取消或结束时,使用CADisplayLink补过缺失的过渡动画

case .cancelled, .ended: if progress > 0.4 { startLink() } else { interactive?.cancel() interactive = nil }

另外我想present A用A动画,B用B动画,C用系统的怎么办...

//注意block用要用weak,因为互相包含了weak var weakSelf = selfanimator.setup(panGestureVC: self, transitionAction: { weakSelf?.myAction { (fromVC, toVC, operation) -> Dictionary<String, Any>? in switch operation { case .present: if toVC is A { return ["duration" : "0.4", "delegate" : APresentAnimation()] } else if toVC is B { return ["duration" : "0.4", "delegate" : BPresentAnimation()] } else if toVC is C { return nil } default: break } return nil}

iOS场景对应的类是ViewController,基本上一个场景对应一个VC,从一个场景切换到另一个场景,基本是ViewController之间的切换,这切换过程的动画成为transition animation,过渡动画、转场动画。

push和pop转场动画基本流程

新葡京8455 5

push和pop转场动画基本流程图

Note:

  • 动画的状态和转场的状态是不一样的,动画完成后,不代表转场完成,所以我们要在动画的completion里面决定是否完成转场:[transitionContext completeTransition:!transitionContext.transitionWasCancelled];
  • 转场是一个过程,所有的动画都在containerView里面完成。
  • 不需要交互的转场interactionControllerForAnimationController方法一定要返回nil

✨文章的源码将在完成完成下篇present和dismiss动画时候上传。

本文作为读书笔记,不是科普读物,所以知识有可能理解错误,如有请您不吝赐教。

Demo地址:https://github.com/cnthinkcode/HSPresentTransitionDemo

3、UIViewControllerAnimatedTransitioning

转场动画的实现,想怎么酷炫就靠他了

//动画时长public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval

//执行动画的方法,根据transitionContext上下文能获取fromVC和toVCpublic func animateTransition(using transitionContext: UIViewControllerContextTransitioning)

准备写两篇,第一篇介绍下转场动画,第二篇介绍下我封装的一个转场动画的库,可以很简便的给VC之间的转变加上自定义动画。

//类似这种功能的实现,理清功能流程是重点。//这种功能实现方式应该有很多种,这里我只讲我的实现方式。

iOS7.0后苹果提供了自定义转场动画的API,利用这些API我们可以改变 push和pop(navigation非模态),present和dismiss(模态),标签切换(tabbar)的默认转场动画。

1、UINavigationControllerDelegate

push和pop转场动画协议,主要用到的方法有两个

optional public func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?

参数navigationController当前执行动画的导航栏operation用来判断push还是pop来实现不同动画fromVC和toVC,A push B,A是fromVC,B是toVC,B pop,B是fromVC,A是toVC,顾名思义没什么好说的最后返回需要执行的动画

optional public func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?

这个方法是用来支持手势驱动的,第一个参数同上,animationController当前执行的动画,返回UIViewControllerInteractiveTransitioning用来控制手势交互的对象

1、转场动画

添加自定义动画非常简单,就是给系统的切换过程提供动画,其他的不变。
1.1 对于navigation的切换
UINavigationController的delgate具有一个方法,可以用来个给它提供动画:

    //页面推进的动画
    var presentationTransition : UIViewControllerAnimatedTransitioning?
    //页面返回的动画
    var dismissionTransition : UIViewControllerAnimatedTransitioning?

//UINavigationController的delegate方法
func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {

        if operation == .Push {

            return presentationTransition

        }else if operation == .Pop{
            return dismissionTransition
        }

        return nil
    }

看这个委托方法,很容易明白它的设计意图,这个委托是UINavigationController的,它负责控制VC切换,切换的时候,它向自己的delegate调用这个方法,看有没有自定义的动画,有,那么接下换切换VC就用自定义的动画;没有就用系统准备的动画。
对UINavigationController可能有这么一段:

...开始pushpop了
var animatedTransitioning : UIViewControllerAnimatedTransitioning?
if self.delegate.respondsToSelector("navigationController:animationControllerForOperation:fromViewController:toViewController"){
   animatedTransitioning =self.navigationController?.delegate?.navigationController(self, animationControllerForOperation: .Push, fromViewController: fromVC, toViewController: toVC)
}

if animatedTransitioning == nil{
  animatedTransitioning = 系统预备的动画
}
...之后使用动画animatedTransitioning

在这不得不说,这个接口开得恰到好处,把动画相关的都开放出来,我们自由定义,而其他的都隐藏着,不需要我们操心。

1.2 present/dismiss类型的切换
这个逻辑上就一样了,A -present-> B 或者 B -dismiss->A,那么要指定B的transitioningDelegate,实现里面的两个委托提供动画:

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

        return presentationTransition
    }

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

跟上面navigation方式的切换时一样,也是给控制转场的角色提供自定的动画,就这么简单

1.3 自定义动画内容
上面提供的动画,都是UIViewControllerAnimatedTransitioning这个类型的(其实这是个protocol哈),具体内容需要建个新类,重写两个方法,代码:

import UIKit

class TFTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {

    var duration : NSTimeInterval
    var completeHander : ((Bool) -> Void)?

    init(duration : NSTimeInterval = 0.25){
        self.duration = duration
        super.init()
    }
    //方法1
    func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
        return duration
    }

    // 方法2
    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
        let toView = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!.view
        let fromView = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!.view
//这句一定要,这时toView是不会自己加上来的
        transitionContext.containerView()?.addSubview(toView)

        UIView.animateWithDuration(duration, delay: 0, options: .CurveLinear, animations: { () -> Void in

            写入fromView的变化...
            写入toView的变化...

            }, completion: { (finished) -> Void in
//结束动画,否则会干扰下次动画
      transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
        })
    }
}

方法1用来提供整个动画的时间,同时也是转场的时间;方法2给了一个transitionContext,从它可以得到fromViewtoViewfromVCtoVC等相关信息。

接下来就是UIView层次的动画了,就变得简单多了,就是调调fromView的alpha啊、toView的frame,transform等等的属性来形成你想要的想过就ok了。

最后,结束了提示转场完成。!transitionContext.transitionWasCancelled()这样写的目的是,如果动画取消了,就不算完成,这样再次执行时,还是开始状态。

#######2. 交互动画

iOS的侧滑返回,是你手滑动一点,界面也切换一点,整个过程是根据你的手势来控制的,不像pushpop,只要开始,就自动执行。而交互动画,就是可以让我们也可以自定义一个方式,比如手向下滑动,来全程控制转场动画的执行。可以实现一些比较吊的效果。

上图:)

交互示意:先是左划present到新的VC,然后用双指缩放dismiss回来

交互动画,一个是交互、一个是动画,思路是这样的:动画有一个过程,比如A->B,那么我给定一个进度,比如0.5的时候,动画就执行到一半的位置,给0.3的时候就执行到30%的进度位置。这样,你在手拖动的时候,不断的更新进度,画面也就根据你的手变化更新画面。

上代码,以navigatonController的转场为例:

//交互式动画的进度管理器
    var interactiveTransition : UIPercentDrivenInteractiveTransition!

func navigationController(navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {

        return interactiveTransition
    }

也是实现一个UINavigationController的委托,提供一个遵循UIViewControllerInteractiveTransitioning协议对象,这里是UIPercentDrivenInteractiveTransition对象,它就是根据进度来确定动画的过程的。

然后手势开启时,开启动画,以左划为例:

//先添加手势
let tapGesture = UITapGestureRecognizer(target: self, action: "interactWith:")
        self.view.addGestureRecognizer(tapGesture)

//手势触发,开始交互动画
    func interactWith(gestureRecognizer : UIGestureRecognizer){

        switch gestureRecognizer.state {
        case .Began:
            //构建交互式动画的管理器
            interactiveTransition =  UIPercentDrivenInteractiveTransition()
            //触发转场,代码1
            self.navigationController?.pushViewController(toViewController, animated: true)

        case .Changed:
            //获取进度,代码2
            let progress = progressOfGestureRecognizer(gestureRecognizer)
            print(progress)
            //更新进度
            interactiveTransition.updateInteractiveTransition(progress)

        case .Ended,.Cancelled:
             //判断动画是回到开头还是结尾,代码3
            if isTowardEndingForGestureRecognizer(gestureRecognizer){
                interactiveTransition.finishInteractiveTransition()

            }else{
                interactiveTransition.cancelInteractiveTransition()

            }

            interactiveTransition = nil

        default:
            break
        }
    }

(1)可以看到,整个过程的触发点在手势上,是手势开始后,才开始push的,看代码1处,就是在手势状态为began时开启push。为啥说这点,因为这决定了整个流程的方向不一样了,之前是我们被动的提供动画,这里是主动的控制流程,如果你不push,界面是不会有反应的、你不更新进度(代码2),界面是不会变的。
(2)代码2,progressOfGestureRecognizer是一个自定义方法,根据手势获取进度,左划时我采用的是:

//使用滑动距离和屏幕宽度之比作为进度
let location = gestureRecognizer.locationInView(keyWindow)
let rate = (touchBeginning.x - location.x) / UIScreen.mainScreen().bounds.width
return max(0, rate)

这样的好处是,如果之前的动画timeFunction设为是线性的(.CurveLinear),那你收拖动多少,新界面就会进来多少,看起来比较自然。
(3)代码3,是判断结尾还是回到开头。你拖动界面,进来一点,可能你又不想push了,松手,这时应该是刚push出来的界面又回去了是吧,其实就是动画又滑到了开头。如果push完成,动画就是到结尾,所以需要一个判断。

最后还有两点:(1)这里使用手势来控制进度,从代码可以看出,其实可以不是手势,只要你更新进度,动画就会变。比如...可以声控,你喊pu...就开始push,然后界面一点点左移,喊道...sh,动画结束,当然这种交互有点怪哈。只是说这里的交互方式和进度调节是分开的,这样也增加的交互多样的可能性。
(2)其实交互动画的委托需要的是一个遵循UIViewControllerInteractiveTransitioning协议的对象,不一定是UIPercentDrivenInteractiveTransition类型,也就是不一定是通过动画的进度来实现交互动画的。实现public func startInteractiveTransition(transitionContext: UIViewControllerContextTransitioning)可以有更丰富的控制吧,不过我懒得去研究了

本文由新葡京8455发布,转载请注明来源

关键词: