项目首页大改版,有个UI需求是这样的:
汽车票、火车票两个tab需要做成梯形的样式。并且切tab的时候,梯形的样式需要随之变化。
这边的梯形绘制是比较简单的,直接用贝塞尔曲线绘制即可:
- (void)refreshBusTabWithSel:(BOOL)bSel
{
if (_busBGLayer) {
[_busBGLayer removeFromSuperlayer];
}
CGFloat differX = -HomeTabDifferX;
UIColor *fillColor = RGBA(0, 0, 0, 0.3);
UIColor *titleColor = def_text_White;
if (bSel) {
differX = HomeTabDifferX;
fillColor = def_text_White;
titleColor = def_BG_Blue;
}
UIBezierPath *pathBus = [UIBezierPath bezierPath];
[pathBus moveToPoint:CGPointMake(0, 0)];
[pathBus addLineToPoint:CGPointMake(HomeProjectTabWidth/2 - differX, 0)];
[pathBus addLineToPoint:CGPointMake(HomeProjectTabWidth/2 + differX, HomeTabHeight)];
[pathBus addLineToPoint:CGPointMake(0 , HomeTabHeight)];
[pathBus closePath];
[pathBus fill];
_busBGLayer = [[CAShapeLayer alloc] init];
_busBGLayer.frame = CGRectMake(0, 0, HomeProjectTabWidth/2 + HomeTabDifferX,HomeTabHeight);
_busBGLayer.path = pathBus.CGPath;
_busBGLayer.fillColor = fillColor.CGColor;
[self.btnBusTab.layer insertSublayer:_busBGLayer atIndex:0];
[self.btnBusTab setTitleColor:titleColor forState:UIControlStateNormal];
}
主要想记录一下的是对于点击区域的处理,如何在梯形变化的时候响应正确的点击事件~
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
[super hitTest:point withEvent:event];
CGFloat minX = HomeProjectTabWidth/2-HomeTabDifferX;
CGFloat maxX = HomeProjectTabWidth/2+HomeTabDifferX;
CGFloat ratio = HomeTabHeight/(HomeTabDifferX*2);
CGFloat pointY = HomeTabHeight - point.y;
if ([self pointInside:point withEvent:event]) {
if (point.x <= minX) {
return _btnBusTab;
}
if (point.x >= maxX) {
return _btnTrainTab;
}
if (_bSelTrainTab) {
NSDebugLog(@"@@@%d",pointY > (point.x-minX)*ratio);
return pointY > (point.x-minX)*ratio ? _btnBusTab : _btnTrainTab;
} else {
NSDebugLog(@"@@@%d",pointY > (point.x-minX)*(-ratio) + HomeTabHeight);
return pointY > (point.x-minX)*(-ratio) + HomeTabHeight ? _btnTrainTab : _btnBusTab;
}
} else {
return [super hitTest:point withEvent:event];
}
}
hitTest:withEvent:
每个UIView对象都有一个 hitTest:withEvent: 方法,这个方法是Hit-Testing过程中最核心的存在,其作用是询问事件在当前视图中的响应者,同时又是作为事件传递的桥梁。
这边重写了两个tab的父View的hitTest方法,需要特殊处理的就是中间那条斜线所在的x坐标范围。
如何处理斜线所在x区域的点击?
利用一次函数(形如y=kx+b)的概念将斜线作为一次函数放置在数学平面坐标系中,通过计算点击的坐标是在函数的上方/下方,来判断点击区域是在汽车票tab还是火车票。
值得注意一下的是,当点击坐标不在这两个tab范围内的时候,需要调用一下[super hitTest:point withEvent:event],以保证点击到坐标范围外可以响应其他本该响应的事件,而不是被我们重写的这个方法消耗掉。
注:如果对hitTest等响应链相关知识不太熟悉的,可以看一下这篇:iOS触摸事件全家桶