English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

iOSでカスタムコントローラー遷移アニメーションpushの詳細

前書き

最近少しの空き時間がありましたので、最近のプロジェクトを整理しました。この記事では、iOS カスタムコントローラーのトランジションアニメーション(プッシュ)に関する内容を紹介し、皆さんに参考にしていただくために共有しました。以下の説明は省略します。詳細については、一緒に見ていきましょう。

イメージ結果:


iOS7 Apple がカスタムトランジションの API を提供し始めました。それ以来、CoreAnimation で実現できるどんなアニメーションも、ViewController の切り替えの間に表示されるようになりました。実現方法は高度に解耦合しており、コードのクリーンさを保ちつつ、他のアニメーションスキームを交換する際には、クラス名を簡単に変更するだけで十分です。高品質なコードがもたらす快適な体験を本当に感じました。

実際にはカスタム遷移アニメーションの教材がたくさんありますが、ここでは皆さんが簡単に理解し、すぐに使えるようにしたいと思っています。

遷移アニメーションはPushとModalの二種類がありますので、カスタム遷移アニメーションも二種類に分かれます。本日はPushについてお話しします。

カスタム遷移アニメーションPush

まずインターフェースを構築し、4ボタン:

- (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];
 }
}

アニメーションを追加:

- (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);63; 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, @1.1,@1.0];
  scaleXAniamtion.keyTimes = @[@0.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];   
 };
}

UIの構築が完了しました:

Push時にカスタムシーン遷移アニメーションを実装したい場合は、UINavigationControllerDelegateプロトコルを守る必要があります。

AppleはUINavigationControllerDelegateの中でいくつかのプロトコルメソッドを提供しており、返り値の型から各メソッドの具体的な機能が明確にわかります。

//カスタムシーン遷移アニメーションを定義するために使用されます
- (nullable id)navigationController:(UINavigationController *)navigationController         animationControllerForOperation:(UINavigationControllerOperation)operation            fromViewController:(UIViewController *)fromVC             toViewController:(UIViewController *toVC NS_AVAILABLE_IOS(7_0);
//このアニメーションにユーザーインタラクションを追加します。
- (nullable id)navigationController:(UINavigationController *)navigationController       interactionControllerForAnimationController:(id) animationController NS_AVAILABLE_IOS(7_0);

最初の方法では、UIViewControllerInteractiveTransitioningプロトコルに準拠するオブジェクトを返し、その中でアニメーションを実装するだけで良い

  • 创建继承自 NSObject 并声明 UIViewControllerAnimatedTransitioning 的动画类。
  • 重载 UIViewControllerAnimatedTransitioning 中的协议方法。
//返回动画时间
- (NSTimeInterval)transitionDuration:(nullable id)transitionContext;
//将动画的代码写到里面即可
- (void)animateTransition:(id)transitionContext;

首先我自定义一个名为LRTransitionPushController的类继承于NSObject并遵守了UIViewControllerAnimatedTransitioning协议

 - (void)animateTransition:(id)transitionContext{  
 self.transitionContext = transitionContext;  
 //获取源控制器 注意不要写成 UITransitionContextFromViewKey
 LRTransitionPushController *fromVc = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
 //获取目标控制器 注意不要写成 UITransitionContextToViewKey
 LRTransitionPopController *toVc = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];  
 //获得容器视图
 UIView *containView = [transitionContext containerView];
 // 都添加到container中。注意顺序 目标控制器的view需要后面添加
 [containView addSubview:fromVc.view];
 [containView addSubview:toVc.view];  
 UIButton *button = fromVc.button;
 //円を描画
 UIBezierPath *startPath = [UIBezierPath bezierPathWithOvalInRect:button.frame];
 //ボタンサイズと十分にスクリーンを覆う半径を持つ二つの円のUIBezierPathインスタンスを作成します;最終的なアニメーションはこれらのベジェパスの間で行われます
 //ボタン中心からスクリーン最も遠い角の点
 CGPoint finalPoint;
 //どの象限にトリガーポイントがあるかを判断
 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);
 //ボタン中心からスクリーン最も遠い角の距離を基に外側に拡散する半径を計算 - ボタン半径
 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"]; 
}

在控制器里面用来自定义转场动画的方法里返回刚才自定义的动画类

- (id)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{  
 if (operation == UINavigationControllerOperationPush) {
  return [LRTranstionAnimationPush new];
 }else{
  return nil;
 }
}

これでカスタムトランジションアニメーションは完了です。

ポップのアニメーションは、プッシュアニメーションを逆転させるだけであり、詳細は説明しませんが、疑問がある場合はコードを確認してください。

スライドバックジェスチャーを追加します。

先ほど述べたように、このメソッドはこのアニメーションにユーザーインタラクションを追加するため、ポップ時のスライドバックを実装する必要があります。

最も簡単な方法は、UIKitが提供するUIPercentDrivenInteractiveTransitionクラスを使用することです。このクラスはUIViewControllerInteractiveTransitioningプロトコルを実装しており、メンはこのクラスのオブジェクトを通じてトランジションアニメーションの完了パーセンテージを指定できます。

//このアニメーションにユーザーインタラクションを追加します。
- (nullable id)navigationController:(UINavigationController *)navigationController       interactionControllerForAnimationController:(id) animationController NS_AVAILABLE_IOS(7_0);

第1ステップ:ジェスチャーを追加します。

 UIPanGestureRecognizer *gestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
 [self.view addGestureRecognizer:gestureRecognizer];

第2ステップ:ユーザーのスライドの変化を通じてアニメーション実行の割合を決定します。

- (void)handlePan:(UIPanGestureRecognizer *)gestureRecognizer {  
 /*UIPercentDrivenInteractiveTransitionのupdateInteractiveTransition:メソッドを呼び出すことで、トランジションアニメーションがどこまで進行したかを制御できます。
  ユーザーのドロースライドが完了したとき、finishInteractiveTransitionまたはcancelInteractiveTransitionを呼び出し、UIKitが残りのアニメーションの半分を自動的に実行します。
  またはアニメーションを最初の状態に戻します。*/   
 if ([gestureRecognizer translationInView:self.view].x>=0) {
  //スライドの割合
  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;
 }  
}

第3ステップ アニメーションにユーザーインタラクションのエージェントメソッドを追加して UIPercentDrivenInteractiveTransition インスタンスを返します

- (id)navigationController:(UINavigationController *)navigationController       interactionControllerForAnimationController:(id) animationController {
 return self.interactiveTransition;
}

この記事があなたに役立ったと感じたら、いいねをお願いします。ありがとうございます。

コードは以下に置いてありますGitHub上記のページでダウンロードできます。もちろん、ローカルダウンロード

まとめ

これでこの記事のすべての内容が終わりました。本文の内容が皆様の学習や仕事に参考になることを願っています。疑問がある場合は、コメントを残してください。皆様の「呐喊教程」へのサポートに感謝します。

声明:本文の内容はインターネットから取得しており、著作権者は所有者であり、インターネットユーザーが自発的に貢献し、自己でアップロードしました。本サイトは所有権を有しておらず、人工編集は行われていません。著作権侵害を疑われる内容がある場合は、メールで notice#w までお知らせください。3codebox.com(メールを送信する際、#を@に変更してください)で通報し、関連する証拠を提供してください。一旦確認が取れましたら、本サイトは即座に侵害を疑われるコンテンツを削除します。

おすすめ