zoukankan      html  css  js  c++  java
  • iOS手势识别的详细使用(拖动,缩放,旋转,点击,手势依赖,自定义手势)

    1、UIGestureRecognizer介绍

    手势识别在iOS上非常重要,手势操作移动设备的重要特征,极大的增加了移动设备使用便捷性。
    iOS系统在3.2以后,为方便开发这使用一些常用的手势,提供了UIGestureRecognizer类。手势识别UIGestureRecognizer类是个抽象类,下面的子类是具体的手势,开发这可以直接使用这些手势识别。
    • Tap(点一下)
    • Pinch(二指往內或往外拨动,平时经常用到的缩放)
    • Rotation(旋转)
    • Swipe(滑动,快速移动)
    • Pan (拖移,慢速移动)
    •  LongPress(长按)
    UIGestureRecognizer的继承关系如下:
     
     

    2、使用手势的步骤

    使用手势很简单,分为两步:
    1. 创建手势实例。当创建手势时,指定一个回调方法,当手势开始,改变、或结束时,回调方法被调用。
    2. 添加到需要识别的View中。每个手势只对应一个View,当屏幕触摸在View的边界内时,如果手势和预定的一样,那就会回调方法。
    ps:一个手势只能对应一个View,但是一个View可以有多个手势。
    建议在真机上运行这些手势,模拟器操作不太方便,可能导致你认为手势失效。

    3、Pan 拖动手势:

    1. UIImageView *snakeImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"snake.png"]];  
    2. snakeImageView.frame = CGRectMake(50, 50, 100, 160);  
    3. UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc]  
    4.                                                 initWithTarget:self  
    5.                                                 action:@selector(handlePan:)];      
    6. [snakeImageView addGestureRecognizer:panGestureRecognizer];  
    7. [self.view setBackgroundColor:[UIColor whiteColor]];  
    8. [self.view addSubview:snakeImageView];  
    新建一个ImageView,然后添加手势
    回调方法:
    1. - (void) handlePan:(UIPanGestureRecognizer*) recognizer  
    2. {  
    3.     CGPoint translation = [recognizer translationInView:self.view];  
    4.     recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,  
    5.                                    recognizer.view.center.y + translation.y);  
    6.     [recognizer setTranslation:CGPointZero inView:self.view];  
    7.       
    8. }  

    4、Pinch缩放手势

    1. UIPinchGestureRecognizer *pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc]  
    2.                                                         initWithTarget:self  
    3.                                                         action:@selector(handlePinch:)];<p class="p1">[<span class="s1">snakeImageView</span> <span class="s2">addGestureRecognizer</span>:pinchGestureRecognizer];</p>  

    1. - (void) handlePinch:(UIPinchGestureRecognizer*) recognizer  
    2. {  
    3.     recognizer.view.transform = CGAffineTransformScale(recognizer.view.transform, recognizer.scale, recognizer.scale);  
    4.     recognizer.scale = 1;  
    5. }  

    5、Rotation旋转手势

    1. UIRotationGestureRecognizer *rotateRecognizer = [[UIRotationGestureRecognizer alloc]  
    2.                                                  initWithTarget:self  
    3.                                                  action:@selector(handleRotate:)];  
    4. [snakeImageView addGestureRecognizer:rotateRecognizer];  
     
    1. - (void) handleRotate:(UIRotationGestureRecognizer*) recognizer  
    2. {  
    3.     recognizer.view.transform = CGAffineTransformRotate(recognizer.view.transform, recognizer.rotation);  
    4.     recognizer.rotation = 0;  
    5. }  
     
     
     
    添加了这几个手势后,运行看效果,程序中的imageView放了一个
                        /^/^
                      _|__|  O|
             /     /~     \_/
              \____|__________/  
                     \_______      
                             `                    
                               |     |                  
                              /      /                    
                             /     /                       \
                           /      /                        
                          /     /                              
                        /     /             _----_              
                       /     /           _-~      ~-_         |   |
                      (      (        _-~    _--_    ~-_     _/   |
                             ~-____-~    _-~    ~-_    ~-_-~    /
                         ~-_           _-~          ~-_       _-~  
                            ~--______-~                ~-___-~
    的图片,在模拟器上拖动是没问题的。缩放和旋转有点问题,估计是因为在模拟器上的模拟的两个接触点距离在imageView的边界外了,所以操作无效果。
    建议在真机上运行这个手势。
    在模拟器上缩放和选择的操作技巧:
    可以把imageView的frame值设置大一点,按住alt键,按下触摸板(不按下不行),这样就可以旋转和缩放了。

    6、添加第二个ImagView并添加手势

    记住:一个手势只能添加到一个View,两个View当然要有两个手势的实例了
    1. - (void)viewDidLoad  
    2. {  
    3.     [super viewDidLoad];  
    4.   
    5.     UIImageView *snakeImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"snake.png"]];  
    6.     UIImageView *dragonImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"dragon.png"]];  
    7.     snakeImageView.frame = CGRectMake(120, 120, 100, 160);  
    8.     dragonImageView.frame = CGRectMake(50, 50, 100, 160);  
    9.     [self.view addSubview:snakeImageView];  
    10.     [self.view addSubview:dragonImageView];  
    11.       
    12.     for (UIView *view in self.view.subviews) {  
    13.         UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc]  
    14.                                                         initWithTarget:self  
    15.                                                         action:@selector(handlePan:)];  
    16.           
    17.         UIPinchGestureRecognizer *pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc]  
    18.                                                             initWithTarget:self  
    19.                                                             action:@selector(handlePinch:)];  
    20.           
    21.         UIRotationGestureRecognizer *rotateRecognizer = [[UIRotationGestureRecognizer alloc]  
    22.                                                          initWithTarget:self  
    23.                                                          action:@selector(handleRotate:)];  
    24.           
    25.         [view addGestureRecognizer:panGestureRecognizer];  
    26.         [view addGestureRecognizer:pinchGestureRecognizer];  
    27.         [view addGestureRecognizer:rotateRecognizer];  
    28.         [view setUserInteractionEnabled:YES];  
    29.     }  
    30.     [self.view setBackgroundColor:[UIColor whiteColor]];       
    31. }  

    多添加了一条龙的view,两个view都能接收上面的三种手势。运行效果如下:
     

    7、拖动(pan手势)速度(以较快的速度拖放后view有滑行的效果)

    如何实现呢?
    1. 监视手势是否结束
    2. 监视触摸的速度
    1. - (void) handlePan:(UIPanGestureRecognizer*) recognizer  
    2. {  
    3.     CGPoint translation = [recognizer translationInView:self.view];  
    4.     recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,  
    5.                                        recognizer.view.center.y + translation.y);  
    6.     [recognizer setTranslation:CGPointZero inView:self.view];  
    7.       
    8.     if (recognizer.state == UIGestureRecognizerStateEnded) {  
    9.           
    10.         CGPoint velocity = [recognizer velocityInView:self.view];  
    11.         CGFloat magnitude = sqrtf((velocity.x * velocity.x) + (velocity.y * velocity.y));  
    12.         CGFloat slideMult = magnitude / 200;  
    13.         NSLog(@"magnitude: %f, slideMult: %f", magnitude, slideMult);  
    14.           
    15.         float slideFactor = 0.1 * slideMult; // Increase for more of a slide  
    16.         CGPoint finalPoint = CGPointMake(recognizer.view.center.x + (velocity.x * slideFactor),  
    17.                                          recognizer.view.center.y + (velocity.y * slideFactor));  
    18.         finalPoint.x = MIN(MAX(finalPoint.x, 0), self.view.bounds.size.width);  
    19.         finalPoint.y = MIN(MAX(finalPoint.y, 0), self.view.bounds.size.height);  
    20.           
    21.         [UIView animateWithDuration:slideFactor*2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{  
    22.             recognizer.view.center = finalPoint;  
    23.         } completion:nil];  
    24.           
    25.     }  
    26.      
    代码实现解析:
    1. 计算速度向量的长度(估计大部分都忘了)这些知识了。
    2. 如果速度向量小于200,那就会得到一个小于的小数,那么滑行会很短
    3. 基于速度和速度因素计算一个终点
    4. 确保终点不会跑出父View的边界
    5. 使用UIView动画使view滑动到终点
    运行后,快速拖动图像view放开会看到view还会在原来的方向滑行一段路。
     

    8、同时触发两个view的手势

    手势之间是互斥的,如果你想同时触发蛇和龙的view,那么需要实现协议

    UIGestureRecognizerDelegate,

     

    1. @interface ViewController : UIViewController<UIGestureRecognizerDelegate>  
    2. @end  
    并在协议这个方法里返回YES。
    1. -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer  
    2. {  
    3.     return YES;  
    4. }  
    把self作为代理设置给手势:

     

    1. panGestureRecognizer.delegate = self;  
    2. pinchGestureRecognizer.delegate = self;  
    3. rotateRecognizer.delegate = self;  
    这样可以同时拖动或旋转缩放两个view了。

    9、tap点击手势

    这里为了方便看到tap的效果,当点击一下屏幕时,播放一个声音。

    为了播放声音,我们加入AVFoundation.framework这个框架。

     

    1. - (AVAudioPlayer *)loadWav:(NSString *)filename {  
    2.     NSURL * url = [[NSBundle mainBundle] URLForResource:filename withExtension:@"wav"];  
    3.     NSError * error;  
    4.     AVAudioPlayer * player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];  
    5.     if (!player) {  
    6.         NSLog(@"Error loading %@: %@", url, error.localizedDescription);  
    7.     } else {  
    8.         [player prepareToPlay];  
    9.     }  
    10.     return player;  
    11. }  
    我会在最后例子代码给出完整代码,添加手势的步骤和前面一样的。

     

    1. #import <UIKit/UIKit.h>  
    2. #import <AVFoundation/AVFoundation.h>  
    3.   
    4. @interface ViewController : UIViewController<UIGestureRecognizerDelegate>  
    5. @property (strong) AVAudioPlayer * chompPlayer;  
    6. @property (strong) AVAudioPlayer * hehePlayer;  
    7.   
    8. @end  

     

    1. - (void)handleTap:(UITapGestureRecognizer *)recognizer {  
    2.     [self.chompPlayer play];  
    3. }  

    运行,点一下某个图,就会播放一个咬东西的声音。

    不过这个点击播放声音有点缺陷,就是在慢慢拖动的时候也会播放。这使得两个手势重合了。怎么解决呢?使用手势的:requireGestureRecognizerToFail方法。

     

    10、手势的依赖性

    在viewDidLoad的循环里添加这段代码:

     

    1. [tapRecognizer requireGestureRecognizerToFail:panGestureRecognizer];  
    意思就是,当如果pan手势失败,就是没发生拖动,才会出发tap手势。这样如果你有轻微的拖动,那就是pan手势发生了。tap的声音就不会发出来了。

     

    11、自定义手势

    自定义手势继承:UIGestureRecognizer,实现下面的方法:

     

    1. – touchesBegan:withEvent:  
    2. – touchesMoved:withEvent:  
    3. – touchesEnded:withEvent:  
    4. - touchesCancelled:withEvent:  

    新建一个类,继承UIGestureRecognizer,代码如下:

    .h文件

     

    1. #import <UIKit/UIKit.h>  
    2. typedef enum {  
    3.     DirectionUnknown = 0,  
    4.     DirectionLeft,  
    5.     DirectionRight  
    6. } Direction;  
    7.   
    8. @interface HappyGestureRecognizer : UIGestureRecognizer  
    9. @property (assign) int tickleCount;  
    10. @property (assign) CGPoint curTickleStart;  
    11. @property (assign) Direction lastDirection;  
    12.   
    13. @end  
    .m文件
    1. #import "HappyGestureRecognizer.h"  
    2. #import <UIKit/UIGestureRecognizerSubclass.h>  
    3. #define REQUIRED_TICKLES        2  
    4. #define MOVE_AMT_PER_TICKLE     25  
    5.   
    6. @implementation HappyGestureRecognizer  
    7.   
    8. - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {  
    9.     UITouch * touch = [touches anyObject];  
    10.     self.curTickleStart = [touch locationInView:self.view];  
    11. }  
    12.   
    13. - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {  
    14.       
    15.     // Make sure we've moved a minimum amount since curTickleStart  
    16.     UITouch * touch = [touches anyObject];  
    17.     CGPoint ticklePoint = [touch locationInView:self.view];  
    18.     CGFloat moveAmt = ticklePoint.x - self.curTickleStart.x;  
    19.     Direction curDirection;  
    20.     if (moveAmt < 0) {  
    21.         curDirection = DirectionLeft;  
    22.     } else {  
    23.         curDirection = DirectionRight;  
    24.     }  
    25.     if (ABS(moveAmt) < MOVE_AMT_PER_TICKLE) return;  
    26.       
    27.     // 确认方向改变了  
    28.     if (self.lastDirection == DirectionUnknown ||  
    29.         (self.lastDirection == DirectionLeft && curDirection == DirectionRight) ||  
    30.         (self.lastDirection == DirectionRight && curDirection == DirectionLeft)) {  
    31.           
    32.         // 挠痒次数  
    33.         self.tickleCount++;  
    34.         self.curTickleStart = ticklePoint;  
    35.         self.lastDirection = curDirection;  
    36.           
    37.         // 一旦挠痒次数超过指定数,设置手势为结束状态  
    38.         // 这样回调函数会被调用。  
    39.         if (self.state == UIGestureRecognizerStatePossible && self.tickleCount > REQUIRED_TICKLES) {  
    40.             [self setState:UIGestureRecognizerStateEnded];  
    41.         }  
    42.     }  
    43.       
    44. }  
    45.   
    46. - (void)reset {  
    47.     self.tickleCount = 0;  
    48.     self.curTickleStart = CGPointZero;  
    49.     self.lastDirection = DirectionUnknown;  
    50.     if (self.state == UIGestureRecognizerStatePossible) {  
    51.         [self setState:UIGestureRecognizerStateFailed];  
    52.     }  
    53. }  
    54.   
    55. - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event  
    56. {  
    57.     [self reset];  
    58. }  
    59.   
    60. - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event  
    61. {  
    62.     [self reset];  
    63. }  
    64.   
    65. @end  

    调用自定义手势和上面一样,回到这样写:

     

    1. - (void)handleHappy:(HappyGestureRecognizer *)recognizer{  
    2.     [self.hehePlayer play];  
    3. }  
    手势成功后播放呵呵笑的声音。
    在真机上运行,按住某个view,快速左右拖动,就会发出笑的声音了。
    代码解析:
    先获取起始坐标:curTickleStart
    通过和ticklePoint的x值对比,得出当前的放下是向左还是向右。再算出移动的x的值是否比MOVE_AMT_PER_TICKLE距离大,如果太则返回。
    再判断是否有三次是不同方向的动作,如果是则手势结束,回调。
     
    参考:http://www.raywenderlich.com/6567/uigesturerecognizer-tutorial-in-ios-5-pinches-pans-and-more
  • 相关阅读:
    yii2 gii 命令行自动生成控制器和模型
    控制器中的方法命名规范
    Vue Property or method "" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based
    IDEA插件:GsonFormat
    Spring Boot : Access denied for user ''@'localhost' (using password: NO)
    Typora添加主题
    Git基础命令图解
    Java Joda-Time 处理时间工具类(JDK1.7以上)
    Java日期工具类(基于JDK1.7版本)
    Oracle SQL Developer 连接Oracle出现【 状态: 失败 -测试失败: ORA-01017: invalid username/password; logon denied】
  • 原文地址:https://www.cnblogs.com/hanzhuzi/p/4064526.html
Copyright © 2011-2022 走看看