這篇文章通過實例實現了一個類似小米手勢遙控器的功能頁面。
效果圖如下所示:
觸摸事件的響應通過對系統的觸摸實踐監聽來進行。
通過一個數組來對點的集合進行緩存和分析。
<喎?/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; }
其他也多用遍歷進行判斷,大部分分析都在一次遍歷以內。例如檢查是不是彈出菜單手勢或者返回手勢。
- (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; }
- (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.點的路徑分析比較簡單,如果對統計有研究會有更出色的分析公式。
以上就是本篇博客全部內容,歡迎指正和評論。