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

كيفية تحديد تحويل التحكم في iOS: تحليل دقيق لـ push

前言

最近有些空闲时间,整理了下最近做的项目,本文主要介绍了关于iOS自定义控制器转场动画push的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。

效果图:


iOS7 开始苹果推出了自定义转场的 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) ? 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];   
 };
}

تم بناء الواجهة:

ثم، إذا كنت ترغب في تنفيذ حركة انتقال مخصصة عند النقر على الزر، يجب أن تتبع بروتوكول 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 实例;一个是 button 的 size ,另外一个则拥有足够覆盖屏幕的راديوس.
 // نقطة الزاوية الأقرب إلى الشاشة من مركز الزر
 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)];  
 //تعيين قيمة لـ maskLayer لـ toVc.view.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)navigatonController:(UINavigationController *)navigatonController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{  
 إذا (operation == UINavigationControllerOperationPush) {
  يعود [LRTranstionAnimationPush جديد];
 } else {
  يعود nil;
 }
}

بهذا تكون عملية التحول المخصصته انتهت

رسوم التحول للتراجع هي مجرد عكس رسوم التحول للتحويل، لذا لن نوضح ذلك بشكل مفصل، يمكنكم الرجوع إلى الكود إذا كان لديكم أي استفسارات

إضافة手势 رجوع السحب

كما ذكرت سابقًا، هذه الطريقة تضيف تفاعل للمسلسل الزمني، لذا يجب علينا تنفيذ رجوع السحب عند التبديل

الطريقة الأبسط هي استخدام فئة UIPercentDrivenInteractiveTransition التي توفرها UIKit، وهي قد قامت بتنفيذ معاهدة UIViewControllerInteractiveTransitioning، يمكن للمستخدمين من خلال هذه الفئة تحديد نسبة إكمال عملية التحويل

//إضافة تفاعل للمسلسل الزمني هذا
- (nullable id)navigationController:(UINavigationController *)navigationController       interactionControllerForAnimationController:(id) animationController NS_AVAILABLE_IOS(7_0);

الخطوة الأولى: إضافة手势

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

الخطوة الثانية: تحديد نسبة تنفيذ الرسوم المتحركة من خلال تغييرات السحب للمستخدم

- (void)handlePan:(UIPanGestureRecognizer *)gestureRecognizer {  
 /*يمكن التحكم في عملية الرسوم المتحركة التحويلية باستخدام طريقة updateInteractiveTransition: من UIPercentDrivenInteractiveTransition،
  عند اكتمال手势 السحب للأسفل للمستخدم، يتم استدعاء finishInteractiveTransition أو cancelInteractiveTransition، حيث يقوم UIKit بتنفيذ النصف المتبقي من الرسوم المتحركة تلقائيًا،
  أو دع الرسوم المتحركة تعود إلى الحالة الأصلية.*/   
 إذا ([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;
 }  
}

الخطوة الثالثة: في دالة وسيط المستخدم لجعل تحويل الرسوم البيانية يتفاعل مع المستخدم، أعد تحويل UIPercentDrivenInteractiveTransition

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

إذا شعرت أن هذا المقال قد ساعدك، فانقر على زر ‘مفضل’، شكرًا لك.

تم وضع الكود فيGitHubيمكنك تنزيله هنا، بالطبع يمكنك أيضًا من خلالتنزيل محلي

الخاتمة

هذا هو محتوى المقال الكامل، آمل أن يكون محتوى هذا المقال له قيمة مرجعية تعليمية أو مهنية بالنسبة لكم، إذا كان لديكم أي أسئلة، يمكنكم ترك تعليقات للتفاعل، شكرًا لدعمكم لتعليم الأناجيل.

إعلان: محتوى هذا المقال تم استيراده من الإنترنت، حقوق النشر تخص صاحب المقال، المحتوى تم تقديمه من قبل مستخدمي الإنترنت بشكل طوعي وتحميله، لا يمتلك هذا الموقع حقوق الملكية، لم يتم تعديل المحتوى بشكل يدوي ولا يتحمل الموقع أي مسؤولية قانونية المتعلقة بذلك. إذا رأيت محتوى يشتبه في حقوق النسخ، يرجى إرسال بريد إلكتروني إلى: notice#oldtoolbag.com (أثناء إرسال البريد الإلكتروني، يرجى استبدال #بـ @) لتقديم الشكوى، وتقديم الأدلة ذات الصلة، وإذا تم التحقق من ذلك، سيقوم الموقع بإزالة المحتوى المزعوم عن حقوق النسخ فورًا.

التي قد تفضلك