日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

3d翻转 ios_iOS自定义转场详解04——实现3D翻转效果

發布時間:2023/12/10 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 3d翻转 ios_iOS自定义转场详解04——实现3D翻转效果 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

這是自定義轉場系列的第四篇。由于具有一定的連續性,我會忽略一些基礎,所以如果你是第一次看這個系列,可以先過目之前的幾篇 ——— UIViewControllerTransitioning的用法 、實現Keynote中的神奇移動效果、實現通過圓圈放大縮小的轉場動畫。

老規矩,先端上GIF。

How to work

首先在StoryBoard上拖兩個UIViewController。并且在第一個VC上放一個button,使用Action Segue連接到第二個VC。

然后回到代碼界面。和以往一樣,我們需要創建兩個文件:一個用于從第一個VC過渡到第二個VC的動畫(如push),另一個這是第二個過渡到第一個VC的動畫(如pop)。這里不得不說iOS7中引入的這種解耦合的方式,它的意義在于無論在哪兒需要用到轉場動畫的地方,直接把這兩個文件扔過去就行了。

我們創建兩個文件:KYPushTransition 和 KYPopTransition 。從名字可以看出,后一個是前一個的反轉動畫。其實,我們完全可以把兩個文件寫在一起:KYTransition 。因為兩個文件的代碼結構幾乎別無二致,不同的地方也只要用布爾值區分一下就行了。但這里為了讓介紹思路清晰,我們把兩個動畫分開來實現。

首先是KYPushTransition。

先繼承 UIViewControllerAnimatedTransitioning 協議。實現下面兩個方法:

- (NSTimeInterval)transitionDuration:(id )transitionContext{

//動畫的時間

return 0.6f;

}

- (void)animateTransition:(id )transitionContext{

//動畫的邏輯

...

}

下面具體介紹動畫的邏輯。

- (void)animateTransition:(id )transitionContext{

//1

FirstViewController *fromVC = (FirstViewController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];

SecondViewController *toVC = (SecondViewController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

UIView *fromView = fromVC.view;

UIView *toView = toVC.view;

UIView *containerView = [transitionContext containerView];

[containerView addSubview:toView];

[containerView sendSubviewToBack:toView];

//2

CATransform3D transform = CATransform3DIdentity;

transform.m34 = -0.002;

containerView.layer.sublayerTransform = transform;

//3

CGRect initialFrame = [transitionContext initialFrameForViewController:fromVC];

fromView.frame = initialFrame;

toView.frame = initialFrame;

//4

[self updateAnchorPointAndOffset:CGPointMake(0.0, 0.5) view:fromView];

//5

CAGradientLayer *gradient = [CAGradientLayer layer];

gradient.frame = fromView.bounds;

gradient.colors = @[(id)[UIColor colorWithWhite:0.0 alpha:0.5].CGColor,

(id)[UIColor colorWithWhite:0.0 alpha:0.0].CGColor];

gradient.startPoint = CGPointMake(0.0, 0.5);

gradient.endPoint = CGPointMake(0.8, 0.5);

UIView *shadow = [[UIView alloc]initWithFrame:fromView.bounds];

shadow.backgroundColor = [UIColor clearColor];

[shadow.layer insertSublayer:gradient atIndex:1];

shadow.alpha = 0.0;

[fromView addSubview:shadow];

//6

[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{

//旋轉fromView 90度

fromView.layer.transform = CATransform3DMakeRotation(-M_PI_2, 0, 1.0, 0);

shadow.alpha = 1.0;

} completion:^(BOOL finished) {

//7

fromView.layer.anchorPoint = CGPointMake(0.5, 0.5);

fromView.layer.position = CGPointMake(CGRectGetMidX([UIScreen mainScreen].bounds), CGRectGetMidY([UIScreen mainScreen].bounds));

fromView.layer.transform = CATransform3DIdentity;

[shadow removeFromSuperview];

[transitionContext completeTransition:YES];

}];

}

解釋一下。

1)通過上下文transitionContext獲得前后兩個UIView,這也是發生動畫的具體對象。同時還需要獲得containerView,這也是動畫發生的地方。我們需要把后一個視圖添加上去。為了保證后一個視圖加上去之后不遮住前一個視圖的動畫,我們還要把后一個視圖放到最后:[containerView sendSubviewToBack:toView];

2)為了保證視圖產生3D的效果,我們需要設置layer的仿射變換。關于仿射變化和m34的概念,推薦一篇博客:iOS的三維透視投影。

3)為fromView、toView設置初始frame。

4)重置錨點。錨點就是視圖旋轉時候的中心,就是那個不動的點。關于錨點以及position的關系,你可以參考這一篇解釋:這將是你最后一次糾結position與anchorPoint!。所以我們在設置了錨點的之后,還需要把layer的position也設置到相應位置:

-(void)updateAnchorPointAndOffset:(CGPoint)anchorPoint view:(UIView *)view{

view.layer.anchorPoint = anchorPoint;

view.layer.position = CGPointMake(0, CGRectGetMidY([UIScreen mainScreen].bounds));

}

方便記憶,你可以理解錨點會吸附到position上。所以光改變錨點不改變position,那么結果就是錨點確實改了,但是position還是在默認的(0.5,0.5),也就是視圖中心。就像這樣:

5)給fromView增加左深右淺的陰影。并且一開始的透明度為0,隨著翻轉的角度變大過渡到1。

6)開始動畫。這這里,我們讓fromView翻轉90度 fromView.layer.transform = CATransform3DMakeRotation(-M_PI_2, 0, 1.0, 0);。這里要注意向外90度是-M_PI_2,y為1.0表示繞著y軸旋轉。

7)動畫結束,我們需要還原錨點的位置、恢復position的位置、恢復layer的transform為CATransform3DIdentity,并且把陰影層移除。由于一開始我沒有沒有恢復錨點和position的位置,而且一直沒找到原因。知道我查看了視圖的層級結構才恍然大悟:

可見,使用控制臺的”Debug View Hierarchy“

是多么有用!

當然,還有最重要最關鍵的一步:[transitionContext completeTransition:YES];。告訴上下文,動畫已經完成。如果你這里不這么做,你將無法從后一個視圖返回前一個視圖。

好了,至此已經完成所有push動畫邏輯。實現pop的邏輯基本無二。

KYPopTransition

1、首先就是刪除[containerView sendSubviewToBack:toView];,這時我們可不想讓動畫躲在后面。

2、第二個不同點,需要加上

//讓toView的截圖旋轉90度

toView.layer.transform = CATransform3DMakeRotation(-M_PI_2, 0.0, 1.0, 0.0);

因為這時動畫的起始應該先保持在90度的位置,然后慢慢過渡到0度。

3、陰影層的需要加在toView上,并且起始透明度應該為1,終止時為0。

How to Use

1、如果你是一個VC present 到另一個VC,那么FirstViewController和SecondViewController都需要繼承協議是UIViewControllerTransitioningDelegate。

然后在FirstViewController中設置代理。

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{

SecondViewController *secVC = (SecondViewController *)segue.destinationViewController;

secVC.transitioningDelegate = self;

[super prepareForSegue:segue sender:sender];

}

并實現協議的兩個方法:分別對應present和dismiss。

- (id )animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source{

KYPushTransition *flip = [KYPushTransition new];

return flip;

}

- (id )animationControllerForDismissedController:(UIViewController *)dismissed{

KYPopTransition *flip = [KYPopTransition new];

return flip;

}

2、如果你用UINavigationController去控制兩個VC,此時如果你什么都不做,segue會自動變成標準的導航欄推進的push、pop。要使用我們自定義的動畫,需要讓FirstViewController繼承協議UINavigationControllerDelegate,然后設置代理為自己self.navigationController.delegate = self;。

實現UINavigationControllerDelegate中的相關協議方法,只需要一個:

- (id )navigationController:(UINavigationController *)navigationController

animationControllerForOperation:(UINavigationControllerOperation)operation

fromViewController:(UIViewController *)fromVC

toViewController:(UIViewController *)toVC{

if (operation == UINavigationControllerOperationPush) {

KYPushTransition *flip = [KYPushTransition new];

return flip;

}else if (operation == UINavigationControllerOperationPop){

KYPopTransition *flip = [KYPopTransition new];

return flip;

}else{

return nil;

}

}

搞定,現在就運行了。

Where to go —— 如何為轉場增加手勢交互

如果是一個VC present 到另一個VC,那么就要實現UIViewControllerTransitioningDelegate中的兩個方法,和UIViewControllerAnimatedTransitioning一樣,分別對應 present 和 dismiss的動畫。

- (id )interactionControllerForPresentation:(id )animator;

- (id )interactionControllerForDismissal:(id )animator;

這時,你可能已經被各種協議代理名字搞暈了,是的,我一開始也暈了,但只要多練幾次還是能就熟悉的。

既然我們之前是創建了兩個文件繼承UIViewControllerAnimatedTransitioning來實現過渡動畫,那么是不是也應該繼承UIViewControllerInteractiveTransitioning來實現百分比交互動畫呢?因為在圖中,兩者的關系是并列的。是的,你可以這么做。但是蘋果提供了一個更好的類 ———— UIPercentDrivenInteractiveTransition。

顧名思義,我們就可以猜到這個類就是專門用來實現手勢百分比交互的。怎么使用呢?

為了遵循解耦合,我們新建一個UIPercentDrivenInteractiveTransition的子類 —— KYPopInteractiveTransition。

創建一個方法-(void)addPopGesture:(UIViewController *)viewController;,用來給目標視圖控制器添加一個邊緣滑動手勢:

-(void)addPopGesture:(UIViewController *)viewController{

presentedVC = viewController;

UIScreenEdgePanGestureRecognizer *edgeGes = [[UIScreenEdgePanGestureRecognizer alloc]initWithTarget:self action:@selector(edgeGesPan:)];

edgeGes.edges = UIRectEdgeLeft;

[viewController.view addGestureRecognizer:edgeGes];

}

實現相應的手勢方法:

-(void)edgeGesPan:(UIScreenEdgePanGestureRecognizer *)edgeGes{

//1

CGFloat translation =[edgeGes translationInView:presentedVC.view].x;

CGFloat percent = translation / (presentedVC.view.bounds.size.width);

percent = MIN(1.0, MAX(0.0, percent));

NSLog(@"%f",percent);

switch (edgeGes.state) {

case UIGestureRecognizerStateBegan:{

//2

self.interacting = YES;

[presentedVC dismissViewControllerAnimated:YES completion:nil];

//如果是navigationController控制,這里應該是[presentedVC.navigationController popViewControllerAnimated:YES];

break;

}

case UIGestureRecognizerStateChanged:{

//3

[self updateInteractiveTransition:percent];

break;

}

case UIGestureRecognizerStateEnded:{

//4

self.interacting = NO;

if (percent > 0.5) {

[self finishInteractiveTransition];

}else{

[self cancelInteractiveTransition];

}

break;

}

default:

break;

}

}

1)計算手指在X軸方向上的偏移距離,與屏幕的寬度的之比保存為一個百分比。也就是說,當手指劃過屏幕的距離超過屏幕寬度的1/2,那么剩下的動畫就自動完成;否則,取消動畫。這里用了MIN和MAX把百分比始終控制在了0~1之間。

2)滑動開始,指定要執行的操作。這里因為沒有使用UINavigation控制兩個VC,所以是dismissViewControllerAnimated:。如果是用UINavigation去控制的,那么這里相應的應該是navigationController popViewControllerAnimated:。 self.interacting的作用稍后揭曉。

3)在UIGestureRecognizerStateChanged 調用 [self updateInteractiveTransition:percent]。這里我們把剛才的百分比傳了過去,系統就可以通過這個0~1的豎數值實時改變動畫的進度。

4)當UIGestureRecognizerStateEnded的時候,我們需要判斷此時手指是否劃過屏幕大于一半的距離。如果大于一半,告訴系統完成:[self finishInteractiveTransition]; 反之,告訴系統取消操作:[self cancelInteractiveTransition],這時動畫也將返回初始位置。

特別注意,當我們使用了手勢百分比交互,在相應的動畫邏輯KYPopTransition中,把原來的[transitionContext completeTransition:YES] 改成 [transitionContext completeTransition:![transitionContext transitionWasCancelled]]。如果一直是YES的話,當我們手指劃過小于屏幕一半,即使系統知道是取消動畫,但在上下文中依然是寫死的YES。

使用手勢百分比交互

在FirstViewController中的-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender里面,創建一個KYPopInteractiveTransition的實例并把SecondVC傳過去:

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{

...

popInteractive = [KYPopInteractiveTransition new];

[popInteractive addPopGesture:secVC];

...

}

然后實現UIViewControllerTransitioningDelegate里的這個關于手勢百分比交互的方法:

- (id )interactionControllerForDismissal:(id )animator{

return popInteractive.interacting ? popInteractive : nil;

}

好了,這里你發現我們使用了popInteractive.interacting來判斷,還記得之前買的關子嗎?self.interacting的作用就在這里。因為- (id )interactionControllerForDismissal:(id )animator 這個方法在 點擊dismiss和滑動dismiss時候都會調用。然而如果只是return popInteractive;的話,當我們點擊dismiss的時候,程序將不會做出反應。所以,我們需要區分,到底是點擊dismiss還是滑動dismiss。因此,我們需要一個布爾值來判斷,就是這樣。

這個系列應該差不多到這里就結束了。本篇的源碼你可以在這里獲得。

有任何疑問,歡迎在下方評論區域留言:D

總結

以上是生活随笔為你收集整理的3d翻转 ios_iOS自定义转场详解04——实现3D翻转效果的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。