這篇文章通過實例實現了一個類似小米手勢遙控器的功能頁面。
效果圖如下所示:



觸摸事件的響應通過對系統的觸摸實踐監聽來進行。
通過一個數組來對點的集合進行緩存和分析。
<喎?/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.點的路徑分析比較簡單,如果對統計有研究會有更出色的分析公式。
以上就是本篇博客全部內容,歡迎指正和評論。