你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> iOS小米遙控器的手勢監聽及UI實現

iOS小米遙控器的手勢監聽及UI實現

編輯:IOS開發綜合


這篇文章通過實例實現了一個類似小米手勢遙控器的功能頁面。

效果圖如下所示:


\

\

\


觸摸事件的響應通過對系統的觸摸實踐監聽來進行。

通過一個數組來對點的集合進行緩存和分析。


<喎?/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];
}


在觸摸結束,結束這次監聽的時候,對這些成員進行了重置,分析手勢,並清空了點數組。

下面則開始進行分析手勢,分析的思路比較簡單。

計算起始點和終點之間的差值,對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.點的路徑分析比較簡單,如果對統計有研究會有更出色的分析公式。



以上就是本篇博客全部內容,歡迎指正和評論。

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved