iOS小米遥控器的手势监听及UI实现

时间:2019-11-30 17:22来源:计算机教程
iOS小米遥控器的手势监听及UI实现 这篇文章通过实例实现了一个类似小米手势遥控器的功能页面。 效果图如下所示: 触摸事件的响应通过对系统的触摸实践监听来进行。 通过一个数组

iOS小米遥控器的手势监听及UI实现

这篇文章通过实例实现了一个类似小米手势遥控器的功能页面。

效果图如下所示:

vnsc5858威尼斯城官网 1

vnsc5858威尼斯城官网 2

vnsc5858威尼斯城官网 3

触摸事件的响应通过对系统的触摸实践监听来进行。

通过一个数组来对点的集合进行缓存和分析。

< 喎?http://www.Bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA PC9wPgo8cHJlIGNsYXNzPQ=="brush:java;">- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { if (!self.allowsInteraction) return; UITouch *touch = [touches anyObject]; CGPoint start = [touch locationInView:self.view]; [_gestureManager beginMonitorWithPoint:start]; [self showLightAtPoint:start]; NSLog(@"touch begin"); } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { if (!self.allowsInteraction) return; UITouch *touch = [touches anyObject]; CGPoint point = [touch locationInView:self.view]; __weak typeof(&*self) weakSelf = self; [_gestureManager updateMonitorWithPoint:point action:^{ [weakSelf showLightAtPoint:point]; }]; }
在触摸开始和移动的时候,通过一个类来对手势相关方法的触发和管理及其他行为。即成员_gestureManager。

- (void)beginMonitorWithPoint:(CGPoint)point
{
    [self addPoint:point];
}

- (void)updateMonitorWithPoint:(CGPoint)point action:(dispatch_block_t)actionBlock
{
    _curTime  ;
    int delta = (int)(_curTime - _lastSpawnTime);

    if (delta >= TIME_GAP) {
        if (actionBlock) {
            actionBlock();
        }

        _lastSpawnTime = _curTime;
        [self addPoint:point];
    }
}

在开始监听后,我们不需要把系统传递的每个点都进行触发贴图显示点的轨迹,所以设置了成员来设置间隙位,已达到对点的密集程度进行控制。

- (void)endMonitor
{
    _curTime = 0;
    _lastSpawnTime = 0;
    [self pathAnalysis];
    [self.pointPath removeAllObjects];
}

在触摸结束,结束这次监听的时候,对这些成员进行了重置,分析手势,并清空了点数组。

vnsc5858威尼斯城官网,下面则开始进行分析手势,分析的思路比较简单。

计算起始点和终点之间的差值,对x,y进行分析,判断方向,再判断有无突出(是否是返回,功能等手势)

- (void)pathAnalysis
{
    int count = self.pointPath.count;
    NSLog(@"points count: %d", count);

    if (count > JUDGE_CONTAIN) {
        goto SendNone;
    } else if (count == 1) {
        [self sendDelegateResult:MonitorResultTypeChosen];
    } else {
        CGPoint start = valueToPoint([self.pointPath firstObject]);
        CGPoint end   = valueToPoint([self.pointPath lastObject]);
        int deltaX = pSub(start, end).x;
        int deltaY = pSub(start, end).y;

        int midIndex = count/2;
        CGPoint mid = valueToPoint(self.pointPath[midIndex]);

        if (abs(deltaX) > JUDGE_X && abs(deltaY) < JUDGE_Y) { // horizontal direction

            if (deltaX < 0) {  //right direction
                if (![self checkIsAlwaysCorrectDirection:MonitorResultTypeRight start:0 end:self.pointPath.count-1]) goto SendNone;
                if (pSub(start, mid).y > JUDGE_Y/2) {
                    if ([self checkTrackIsMenu]) [self sendDelegateResult:MonitorResultTypeMenu];
                    else goto SendNone;
                } else if (abs(pSub(start, mid).y) < JUDGE_Y) {
                    [self sendDelegateResult:MonitorResultTypeRight];
                } else goto SendNone;
            } else {    //left
                if (![self checkIsAlwaysCorrectDirection:MonitorResultTypeLeft start:0 end:self.pointPath.count-1]) goto SendNone;

                if (pSub(start, mid).y > JUDGE_Y/2) {
                    if ([self checkTrackIsMenu]) {
                        [self sendDelegateResult:MonitorResultTypeMenu];
                    } else goto SendNone;
                } else if (abs(pSub(start, mid).y) < JUDGE_Y) {
                    [self sendDelegateResult:MonitorResultTypeLeft];
                } else goto SendNone;

            }
        } else if (abs(deltaX) < JUDGE_X && abs(deltaY) > JUDGE_Y) { // vertical direction

            if (deltaY < 0) {   // down
                if (![self checkIsAlwaysCorrectDirection:MonitorResultTypeDownwards start:0 end:self.pointPath.count-1]) goto SendNone;
                if (pSub(start, mid).x > JUDGE_X/2) {
                    if ([self checkTrackIsBack]) [self sendDelegateResult:MonitorResultTypeBack];
                    else goto SendNone;
                } else if (abs(pSub(start, mid).x) < JUDGE_X) {
                    [self sendDelegateResult:MonitorResultTypeDownwards];
                } else goto SendNone;
            } else {            // up
                if (![self checkIsAlwaysCorrectDirection:MonitorResultTypeUpwards start:0 end:self.pointPath.count-1]) goto SendNone;
                if (abs(pSub(start, mid).x) < JUDGE_X) [self sendDelegateResult:MonitorResultTypeUpwards];
                else goto SendNone;
            }
        } else goto SendNone;
    }
    return;

SendNone:
    [self sendDelegateResult:MonitorResultTypeNone];
    return;
}

还有一些需要用到的函数

UIKIT_STATIC_INLINE UIImageView * quickImageView(NSString * imgName) {
    UIImageView *iv = [[UIImageView alloc] initWithImage:ImageCache(imgName)];
    return iv;
}

UIKIT_STATIC_INLINE CGPoint pSub(CGPoint a, CGPoint b) {
    return CGPointMake(a.x - b.x, a.y - b.y);
}

UIKIT_STATIC_INLINE NSValue * pointToValue(CGPoint a) {
    return [NSValue valueWithCGPoint:a];
}

UIKIT_STATIC_INLINE CGPoint valueToPoint(NSValue *v) {
    return [v CGPointValue];
}

因为这些函数调用频率都比较高,所以就声明为内联静态了。

那些检验是否一个方向,或者是否突出的方法则如下所示:

- (BOOL)checkIsAlwaysCorrectDirection:(MonitorResultType)direct start:(int)start end:(int)end
{
    PathLogicBlock block;
    switch (direct) {
        case MonitorResultTypeRight:
        {
            block = ^(CGPoint v) {
                BOOL ret = (v.x >= 0)? NO: YES;
                return ret;
            };
        }
            break;
        case MonitorResultTypeLeft:
        {
            block = ^(CGPoint v) {
                BOOL ret = (v.x <= 0)? NO: YES;
                return ret;
            };
        }
            break;
        case MonitorResultTypeUpwards:
        {
            block = ^(CGPoint v) {
                BOOL ret = (v.y <= 0)? NO: YES;
                return ret;
            };
        }
            break;
        case MonitorResultTypeDownwards:
        {
            block = ^(CGPoint v) {
                BOOL ret = (v.y >= 0)? NO: YES;
                return ret;
            };
        }
            break;
        default: {return NO;}
            break;
    }

    for (int i = start; i POINT_GAP < end; i  = POINT_GAP) {

        CGPoint s = valueToPoint(self.pointPath[i]);
        CGPoint e = valueToPoint(self.pointPath[i POINT_GAP]);

        CGPoint d = pSub(s, e);

        if (!block(d)) {return NO;}
    }

    return YES;
}

这里通过block设置条件,然后在遍历中进行检查及返回BOOL值。

其他也多用遍历进行判断,大部分分析都在一次遍历以内。例如检查是不是弹出菜单手势或者返回手势。

- (BOOL)checkTrackIsMenu
{
    int start = 0;
    int end = self.pointPath.count-1;
    BOOL flag = NO;

    while (valueToPoint(self.pointPath[start]).y >= valueToPoint(self.pointPath[start 1]).y) {start  ;}
    while (valueToPoint(self.pointPath[end]).y >= valueToPoint(self.pointPath[end-1]).y) {end--;}

    if (abs(start-end) < 2*POINT_GAP) { flag = YES; }

    return flag;
}

- (BOOL)checkTrackIsBack
{
    int start = 0;
    int end = self.pointPath.count-1;
    BOOL flag = NO;

    while (valueToPoint(self.pointPath[start]).x >= valueToPoint(self.pointPath[start 1]).x) {start  ;}
    while (valueToPoint(self.pointPath[end]).x >= valueToPoint(self.pointPath[end-1]).x) {end--;}

    if (abs(start-end) < 2*POINT_GAP) { flag = YES; }

    return flag;
}

在图片显示方面,我对要用到的图片在Controller加载之后进行了预加载。

- (void)loadGestureManager
{
    _gestureManager = [MIGestureManager sharedManager];
    _gestureManager.delegate = self;
    [_gestureManager preloadResources];
}
//gesture manager method
- (void)preloadResources
{
    for (int i = 0; i < INITIAL_COUNT; i  ) {
        UIImageView *iv = quickImageView(PointImage);
        [self.imageSet addObject:iv];
    }

    _upImageView     = quickImageView(UpwardsImage);
    _downImageView   = quickImageView(DownwardsImage);
    _leftImageView   = quickImageView(LeftImage);
    _rightImageView  = quickImageView(RightImage);
    _homeImageView   = quickImageView(HomeImage);
    _backImageView   = quickImageView(BackImage);
    _menuImageView   = quickImageView(MenuImage);
    _chosenImageView = quickImageView(chosenImages[0]);

    NSMutableArray *aniArr = [NSMutableArray array];
    for (int i = 0; i < 4; i  ) {
        UIImage *image = ImageCache(chosenImages[i]);
        [aniArr addObject:image];
    }
    _chosenImageView.animationImages = aniArr;
    _chosenImageView.animationDuration = 0.7;
    _chosenImageView.animationRepeatCount = 1;
}

视图层次问题:

我们看小米遥控器的效果是都在一个网格下面,这里就是在显示点轨迹的视图上覆盖了一层网格视图,以达到那样的效果。

源代码地址:Rannie/MIRemoteControl

当然,这个项目里也有很多要解决的问题,在项目Readme.md中也有提到:

1.点的集合通过系统自带的NSMutableArray来维护,由于不能存结构体,导致需要不停的封包拆包动作如下:

static inline NSValue * pointToValue(CGPoint a) {
return [NSValue valueWithCGPoint:a];
}

static inline CGPoint valueToPoint(NSValue *v) {
return [v CGPointValue];
}
可以通过自己实现数据结构来维护点顺序集合。

2.贴图使用的是UIImageView,可以通过轻量级一些的layer设置content来实现。

3.这里监听的是控制器中的touch事件,也可以通过子类化UIGestureRecognizer来监听UITouch,需要导入一个UIGestureRecognizer子类化的一个头文件即可监听touch事件。具体可以看Using UIGestureRecognizer with Swift Tutorial

4.点的路径分析比较简单,如果对统计有研究会有更出色的分析公式。

以上就是本篇博客全部内容,欢迎指正和评论。

http://www.bkjia.com/IOSjc/884525.htmlwww.bkjia.comtruehttp://www.bkjia.com/IOSjc/884525.htmlTechArticleiOS小米遥控器的手势监听及UI实现 这篇文章通过实例实现了一个类小米手势遥控器的功能页面。 效果图如下所示: 触摸事件的响应通过对...

1.4 UIGestureRecognizer

点击手势——UITapGestureRecognizer
捏合手势——UIPinchGestureRecognizer
拖拽手势——UIPanGestureRecognzer
滑动手势——UISwipeGestureRecognizer
旋转手势——UIRotationGestureRecognizer
长按手势——UILongPressGestureRecognizer

demo可以看看这里:https://github.com/Ezreallol/PhotoBrowse.git 是一个图片浏览器 包括了 点击 捏合 长按手势的使用

1.2 事件分发(Event Delivery)

第一响应者(First responder)指的是当前接受触摸的响应者对象(通常是一个UIView对象),即表示当前该对象正在与用户交互,它是响应者链的开端。整个响应者链和事件分发的使命都是找出第一响应者。

UIWindow对象以消息的形式将事件发送给第一响应者,使其有机会首先处理事件。如果第一响应者没有进行处理,系统就将事件(通过消息)传递给响应者链中的下一个响应者,看看它是否可以进行处理。

iOS系统检测到手指触摸(Touch)操作时会将其打包成一个UIEvent对象,并放入当前活动Application的事件队列,单例的UIApplication会从事件队列(队列先进先出,能保证先产生的事件先处理。)中取出触摸事件并传递给单例的UIWindow来处理,UIWindow对象首先会使用hitTest:withEvent:方法寻找此次Touch操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,这个过程称之为hit-test view。

UIWindow实例对象会首先在它的内容视图上调用hitTest:withEvent:,此方法会在其视图层级结构中的每个视图上调用pointInside:withEvent:(该方法用来判断点击事件发生的位置是否处于当前视图范围内,以确定用户是不是点击了当前视图),如果pointInside:withEvent:返回YES,则继续<em>逐级调用</em>,直到找到touch操作发生的位置,这个视图也就是要找的hit-test view。

hitTest:withEvent:方法的处理流程如下

1.首先调用当前视图的pointInside:withEvent:方法判断触摸点是否在当前视图内;

若返回NO,则hitTest:withEvent:返回nil;

若返回YES,则向当前视图的所有子视图(subviews)发送hitTest:withEvent:消息,所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从subviews数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕;

若第一次有子视图返回非空对象,则hitTest:withEvent:方法返回此对象,处理结束;

如所有子视图都返回非,则hitTest:withEvent:方法返回自身(self)。
1.3 响应触碰方法
<pre>`
//当用户手指开始接触控件或窗口事件激发该方法.
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{

    NSInteger fingersNum = [touches count];
    NSLog(@"触碰手指数%ld",(long)fingersNum);

    NSInteger tapCount = [[touches anyObject]tapCount];
    NSLog(@"点击次数%ld",tapCount);

}
//当用户手指结束触碰控件时激发
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"手指离开屏幕");
}
//在控件上移动时
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"正在移动");
}
//中止了触碰事件时激发
-(void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"中止触碰事件");
}

`</pre>

编辑:计算机教程 本文来源:iOS小米遥控器的手势监听及UI实现

关键词: