您好,登錄后才能下訂單哦!
前言
最近有些空閑時(shí)間,整理了下最近做的項(xiàng)目,本文主要介紹了關(guān)于iOS自定義控制器轉(zhuǎn)場(chǎng)動(dòng)畫(huà)push的相關(guān)內(nèi)容,分享出來(lái)供大家參考學(xué)習(xí),下面話不多說(shuō)了,來(lái)一起看看詳細(xì)的介紹吧。
效果圖:
iOS7 開(kāi)始蘋(píng)果推出了自定義轉(zhuǎn)場(chǎng)的 API 。從此,任何可以用 CoreAnimation 實(shí)現(xiàn)的動(dòng)畫(huà),都可以出現(xiàn)在兩個(gè) ViewController 的切換之間。并且實(shí)現(xiàn)方式高度解耦,這也意味著在保證代碼干凈的同時(shí)想要替換其他動(dòng)畫(huà)方案時(shí)只需簡(jiǎn)單改一個(gè)類(lèi)名就可以了,真正體會(huì)了一把高顏值代碼帶來(lái)的愉悅感。
其實(shí)網(wǎng)上關(guān)于自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)的教程很多,這里我是希望同學(xué)們能易懂,易上手。
轉(zhuǎn)場(chǎng)分兩種Push和Modal,所以自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)也就肯定分兩種,今天我們講的是Push
自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)Push
首先搭建界面,添加4個(gè)按鈕:
- (void)addButton{ self.buttonArr = [NSMutableArray array]; CGFloat margin=50; CGFloat width=(self.view.frame.size.width-margin*3)/2; CGFloat height = width; CGFloat x = 0; CGFloat y = 0; //列 NSInteger col = 2; for (NSInteger i = 0; i < 4; i ++) { x = margin + (i%col)*(margin+width); y = margin + (i/col)*(margin+height) + 150; UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; button.frame = CGRectMake(x, y, width, height); button.layer.cornerRadius = width * 0.5; [button addTarget:self action:@selector(btnclick:) forControlEvents:UIControlEventTouchUpInside]; button.backgroundColor = [UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:1.0]; button.tag = i+1; [self.view addSubview:button]; [self.buttonArr addObject:button]; } }
添加動(dòng)畫(huà):
- (void)setupButtonAnimation{ [self.buttonArr enumerateObjectsUsingBlock:^(UIButton * _Nonnull button, NSUInteger idx, BOOL * _Nonnull stop) { // positionAnimation CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; positionAnimation.calculationMode = kCAAnimationPaced; positionAnimation.fillMode = kCAFillModeForwards; positionAnimation.repeatCount = MAXFLOAT; positionAnimation.autoreverses = YES; positionAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; positionAnimation.duration = (idx == self.buttonArr.count - 1) ? 4 : 5+idx; UIBezierPath *positionPath = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(button.frame, button.frame.size.width/2-5, button.frame.size.height/2-5)]; positionAnimation.path = positionPath.CGPath; [button.layer addAnimation:positionAnimation forKey:nil]; // scaleXAniamtion CAKeyframeAnimation *scaleXAniamtion = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.x"]; scaleXAniamtion.values = @[@1.0,@1.1,@1.0]; scaleXAniamtion.keyTimes = @[@0.0,@0.5,@1.0]; scaleXAniamtion.repeatCount = MAXFLOAT; scaleXAniamtion.autoreverses = YES; scaleXAniamtion.duration = 4+idx; [button.layer addAnimation:scaleXAniamtion forKey:nil]; // scaleYAniamtion CAKeyframeAnimation *scaleYAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.y"]; scaleYAnimation.values = @[@1,@1.1,@1.0]; scaleYAnimation.keyTimes = @[@0.0,@0.5,@1.0]; scaleYAnimation.autoreverses = YES; scaleYAnimation.repeatCount = YES; scaleYAnimation.duration = 4+idx; [button.layer addAnimation:scaleYAnimation forKey:nil]; }]; }
界面搭建好了:
然后想在Push的時(shí)候?qū)崿F(xiàn)自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)首先要遵守一個(gè)協(xié)議UINavigationControllerDelegate
蘋(píng)果在 UINavigationControllerDelegate 中給出了幾個(gè)協(xié)議方法,通過(guò)返回類(lèi)型就可以很清楚地知道各自的具體作用。
//用來(lái)自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà) - (nullable id)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC NS_AVAILABLE_IOS(7_0);
//為這個(gè)動(dòng)畫(huà)添加用戶交互 - (nullable id)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id) animationController NS_AVAILABLE_IOS(7_0);
在第一個(gè)方法里只要返回一個(gè)準(zhǔn)守UIViewControllerInteractiveTransitioning協(xié)議的對(duì)象,并在里面實(shí)現(xiàn)動(dòng)畫(huà)即可
//返回動(dòng)畫(huà)時(shí)間 - (NSTimeInterval)transitionDuration:(nullable id)transitionContext; //將動(dòng)畫(huà)的代碼寫(xiě)到里面即可 - (void)animateTransition:(id)transitionContext;
首先我自定義一個(gè)名為L(zhǎng)RTransitionPushController的類(lèi)繼承于NSObject準(zhǔn)守了UIViewControllerAnimatedTransitioning協(xié)議
- (void)animateTransition:(id)transitionContext{ self.transitionContext = transitionContext; //獲取源控制器 注意不要寫(xiě)成 UITransitionContextFromViewKey LRTransitionPushController *fromVc = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; //獲取目標(biāo)控制器 注意不要寫(xiě)成 UITransitionContextToViewKey LRTransitionPopController *toVc = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; //獲得容器視圖 UIView *containView = [transitionContext containerView]; // 都添加到container中。注意順序 目標(biāo)控制器的view需要后面添加 [containView addSubview:fromVc.view]; [containView addSubview:toVc.view]; UIButton *button = fromVc.button; //繪制圓形 UIBezierPath *startPath = [UIBezierPath bezierPathWithOvalInRect:button.frame]; //創(chuàng)建兩個(gè)圓形的 UIBezierPath 實(shí)例;一個(gè)是 button 的 size ,另外一個(gè)則擁有足夠覆蓋屏幕的半徑。最終的動(dòng)畫(huà)則是在這兩個(gè)貝塞爾路徑之間進(jìn)行的 //按鈕中心離屏幕最遠(yuǎn)的那個(gè)角的點(diǎn) CGPoint finalPoint; //判斷觸發(fā)點(diǎn)在那個(gè)象限 if(button.frame.origin.x > (toVc.view.bounds.size.width / 2)){ if (button.frame.origin.y < (toVc.view.bounds.size.height / 2)) { //第一象限 finalPoint = CGPointMake(0, CGRectGetMaxY(toVc.view.frame)); }else{ //第四象限 finalPoint = CGPointMake(0, 0); } }else{ if (button.frame.origin.y < (toVc.view.bounds.size.height / 2)) { //第二象限 finalPoint = CGPointMake(CGRectGetMaxX(toVc.view.frame), CGRectGetMaxY(toVc.view.frame)); }else{ //第三象限 finalPoint = CGPointMake(CGRectGetMaxX(toVc.view.frame), 0); } } CGPoint startPoint = CGPointMake(button.center.x, button.center.y); //計(jì)算向外擴(kuò)散的半徑 = 按鈕中心離屏幕最遠(yuǎn)的那個(gè)角距離 - 按鈕半徑 CGFloat radius = sqrt((finalPoint.x-startPoint.x) * (finalPoint.x-startPoint.x) + (finalPoint.y-startPoint.y) * (finalPoint.y-startPoint.y)) - sqrt(button.frame.size.width/2 * button.frame.size.width/2 + button.frame.size.height/2 * button.frame.size.height/2); UIBezierPath *endPath = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(button.frame, -radius, -radius)]; //賦值給toVc視圖layer的mask CAShapeLayer *maskLayer = [CAShapeLayer layer]; maskLayer.path = endPath.CGPath; toVc.view.layer.mask = maskLayer; CABasicAnimation *maskAnimation =[CABasicAnimation animationWithKeyPath:@"path"]; maskAnimation.fromValue = (__bridge id)startPath.CGPath; maskAnimation.toValue = (__bridge id)endPath.CGPath; maskAnimation.duration = [self transitionDuration:transitionContext]; maskAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; maskAnimation.delegate = self; [maskLayer addAnimation:maskAnimation forKey:@"path"]; }
在控制器里面用來(lái)自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)的方法里返回剛才自定義的動(dòng)畫(huà)類(lèi)
- (id)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{ if (operation == UINavigationControllerOperationPush) { return [LRTranstionAnimationPush new]; }else{ return nil; } }
到此為止自定義轉(zhuǎn)場(chǎng)動(dòng)畫(huà)就完成了
pop的動(dòng)畫(huà)只是把push動(dòng)畫(huà)反過(guò)來(lái)做一遍這里就不細(xì)講了,有疑問(wèn)的可以去看代碼
添加滑動(dòng)返回手勢(shì)
上面說(shuō)到這個(gè)方法是為這個(gè)動(dòng)畫(huà)添加用戶交互的所以我們要在pop時(shí)實(shí)現(xiàn)滑動(dòng)返回
最簡(jiǎn)單的方式應(yīng)該就是利用UIKit提供的UIPercentDrivenInteractiveTransition類(lèi)了,這個(gè)類(lèi)已經(jīng)實(shí)現(xiàn)了UIViewControllerInteractiveTransitioning協(xié)議,同學(xué)men可以通過(guò)這個(gè)類(lèi)的對(duì)象指定轉(zhuǎn)場(chǎng)動(dòng)畫(huà)的完成百分比。
//為這個(gè)動(dòng)畫(huà)添加用戶交互 - (nullable id)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id) animationController NS_AVAILABLE_IOS(7_0);
第一步 添加手勢(shì)
UIPanGestureRecognizer *gestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)]; [self.view addGestureRecognizer:gestureRecognizer];
第二步 通過(guò)用戶滑動(dòng)的變化確定動(dòng)畫(huà)執(zhí)行的比例
- (void)handlePan:(UIPanGestureRecognizer *)gestureRecognizer { /*調(diào)用UIPercentDrivenInteractiveTransition的updateInteractiveTransition:方法可以控制轉(zhuǎn)場(chǎng)動(dòng)畫(huà)進(jìn)行到哪了, 當(dāng)用戶的下拉手勢(shì)完成時(shí),調(diào)用finishInteractiveTransition或者cancelInteractiveTransition,UIKit會(huì)自動(dòng)執(zhí)行剩下的一半動(dòng)畫(huà), 或者讓動(dòng)畫(huà)回到最開(kāi)始的狀態(tài)。*/ if ([gestureRecognizer translationInView:self.view].x>=0) { //手勢(shì)滑動(dòng)的比例 CGFloat per = [gestureRecognizer translationInView:self.view].x / (self.view.bounds.size.width); per = MIN(1.0,(MAX(0.0, per))); if (gestureRecognizer.state == UIGestureRecognizerStateBegan) { self.interactiveTransition = [UIPercentDrivenInteractiveTransition new]; [self.navigationController popViewControllerAnimated:YES]; } else if (gestureRecognizer.state == UIGestureRecognizerStateChanged){ if([gestureRecognizer translationInView:self.view].x ==0){ [self.interactiveTransition updateInteractiveTransition:0.01]; }else{ [self.interactiveTransition updateInteractiveTransition:per]; } } else if (gestureRecognizer.state == UIGestureRecognizerStateEnded || gestureRecognizer.state == UIGestureRecognizerStateCancelled){ if([gestureRecognizer translationInView:self.view].x == 0){ [self.interactiveTransition cancelInteractiveTransition]; self.interactiveTransition = nil; }else if (per > 0.5) { [ self.interactiveTransition finishInteractiveTransition]; }else{ [ self.interactiveTransition cancelInteractiveTransition]; } self.interactiveTransition = nil; } } else if (gestureRecognizer.state == UIGestureRecognizerStateChanged){ [self.interactiveTransition updateInteractiveTransition:0.01]; [self.interactiveTransition cancelInteractiveTransition]; } else if ((gestureRecognizer.state == UIGestureRecognizerStateEnded || gestureRecognizer.state == UIGestureRecognizerStateCancelled)){ self.interactiveTransition = nil; } }
第三步 在為動(dòng)畫(huà)添加用戶交互的代理方法里返回UIPercentDrivenInteractiveTransition的實(shí)例
- (id)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id) animationController { return self.interactiveTransition; }
如果感覺(jué)這篇文章對(duì)您有所幫助,順手點(diǎn)個(gè)喜歡,謝謝啦
代碼放在了GitHub上大家可以下載,當(dāng)然也可以通過(guò)本地下載
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)億速云的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。