最近看了苹果自带应用时钟上的时间选择工具感觉挺巧妙的,就尝试着模仿它做出一个控件工具。工程Demo运行效果如下:
AppleAlram.gif
根据时钟选择工具上面的功能,大概可以确定,圆环的绘制我们可以通过CAShapeLayer结合UIBezierPath绘制出来,当拖动起始点或者结束点View时,通过手势判断拖动的角度,从而改变UIBezierPath的角度,并且让起始点或者结束点View根据拖动的角度对中心点进行公转,并且自身进行自转。如果拖动圆环的话,改变UIBezierPath角度的同时,还要让起始点和结束点View同时进行公转和自转。
1.时钟AlarmView
时钟View是该控件的核心区,里面包含了图形的绘制和旋转,旋转类型的判断等多种处理。
核心代码:
-(CAShapeLayer *)alramLayer{
if (!_alramLayer) {
_alramLayer = [[CAShapeLayer alloc] init];
_alramLayer.bounds = CGRectMake(0,0, self.bounds.size.width, self.bounds.size.height);
_alramLayer.path = [self drawAlarmPathWithStartAngle:self.startAngle endAngle:self.endAngle].CGPath;
_alramLayer.fillColor = UIColor.orangeColor.CGColor;
}
return _alramLayer;
}
#pragma mark - Method - Method -
-(void)beiginRotationWithAngle:(CGFloat)angle beiginPiont:(CGPoint)point{
switch (self.rotationType) {
case kRotationType_StartAngle:
[self changeStartAngle:angle];
break;
case kRotationType_EndAngle:
[self changeEndAngle:angle];
break;
case kRotationType_CircularingLocation:
[self changeCircularingLocation:angle];
break;
default:
break;
}
}
/** 改变起始时间 */
-(void)changeStartAngle:(CGFloat)startAngle{
NSLog(@"角度差 = %f",fabs(self.endAngle - self.startAngle - startAngle));
if (fabs(self.endAngle - self.startAngle - startAngle) >360) {//修复BUG
if (startAngle > 0) {
startAngle = startAngle -360;
}else{
startAngle = startAngle +360;
}
}
NSLog(@"角度差2 = %f",fabs(self.endAngle - self.startAngle - startAngle));
self.sleepSuperView.transform = CGAffineTransformMakeRotation(kDgreesToRadoans(self.startAngle+startAngle));//公转
self.sleepView.transform = CGAffineTransformMakeRotation(-kDgreesToRadoans(self.startAngle+startAngle));//自转
self.alramLayer.path = [self drawAlarmPathWithStartAngle:startAngle+self.startAngle endAngle:self.endAngle].CGPath;
}
/** 改变结束时间 */
-(void)changeEndAngle:(CGFloat)endAngle{
if (fabs(self.startAngle - self.endAngle - endAngle) >360) {
if (endAngle > 0) {
endAngle = endAngle -360;
}else{
endAngle = endAngle +360;
}
}
NSLog(@"角度差 = %f",fabs(self.endAngle - self.startAngle - endAngle));
self.ringSuperView.transform = CGAffineTransformMakeRotation(kDgreesToRadoans(self.endAngle+endAngle));
self.ringView.transform = CGAffineTransformMakeRotation(-kDgreesToRadoans(self.endAngle+endAngle));
self.alramLayer.path = [self drawAlarmPathWithStartAngle:self.startAngle endAngle:self.endAngle+endAngle].CGPath;
}
/** 改变圆环位置 */
-(void)changeCircularingLocation:(CGFloat)angle{
self.sleepSuperView.transform = CGAffineTransformMakeRotation(kDgreesToRadoans(self.startAngle+angle));//公转
self.sleepView.transform = CGAffineTransformMakeRotation(-kDgreesToRadoans(self.startAngle+angle));//自转
self.ringSuperView.transform = CGAffineTransformMakeRotation(kDgreesToRadoans(self.endAngle+angle));
self.ringView.transform = CGAffineTransformMakeRotation(-kDgreesToRadoans(self.endAngle+angle));
self.alramLayer.path = [self drawAlarmPathWithStartAngle:self.startAngle+angle endAngle:self.endAngle +angle].CGPath;
}
/** 旋转类型 */
-(kRotationType)rotationTypeWithPiont:(CGPoint)piont{
CGPoint alarmViewCenter = CGPointMake(kAlarmViewRadius, kAlarmViewRadius);
CGPoint startCenter = CGPointMake(cos(((_currentStartAngle-90)/180)*M_PI)*(kAlarmViewRadius-kIconViewHW/2) +kAlarmViewRadius, sin(((_currentStartAngle-90)/180)*M_PI)*(kAlarmViewRadius-kIconViewHW/2) +kAlarmViewRadius);
CGPoint endCenter = CGPointMake(cos(((_currentEndAngle-90)/180)*M_PI)*(kAlarmViewRadius-kIconViewHW/2) +kAlarmViewRadius, sin(((_currentEndAngle-90)/180)*M_PI)*(kAlarmViewRadius-kIconViewHW/2) +kAlarmViewRadius);
if ([UIView distanceBetweenPointA:alarmViewCenter AndPiontB:piont] >= kAlarmViewRadius-kIconViewHW && [UIView distanceBetweenPointA:alarmViewCenter AndPiontB:piont] <= kAlarmViewRadius) {
if ([UIView distanceBetweenPointA:startCenter AndPiontB:piont] < kIconViewHW/2) {
return kRotationType_StartAngle;
}else if ([UIView distanceBetweenPointA:endCenter AndPiontB:piont] < kIconViewHW/2){
return kRotationType_EndAngle;
}else{
return kRotationType_CircularingLocation;
}
}
return kRotationType_None;
}
/** 绘制BezierPath */
-(UIBezierPath *)drawAlarmPathWithStartAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle{
CGRect circleRect = CGRectMake(kAlarmViewRadius,kAlarmViewRadius, self.bounds.size.width, self.bounds.size.height);
UIBezierPath* circlePath = [UIBezierPath bezierPath];
[circlePath addArcWithCenter: CGPointMake(CGRectGetMidX(circleRect), CGRectGetMidY(circleRect)) radius: circleRect.size.width/2 startAngle: kDgreesToRadoans(startAngle) endAngle: kDgreesToRadoans(endAngle) clockwise: YES];
[circlePath addLineToPoint: CGPointMake(CGRectGetMidX(circleRect), CGRectGetMidY(circleRect))];
[circlePath closePath];
_currentStartAngle = fmodf(startAngle,360);
_currentEndAngle = fmodf(endAngle, 360);
self.costTimeLbl.attributedText = [self timeBlockWithAngle:_currentEndAngle - _currentStartAngle];
self.beginTime = [self beginTimeWithAngle:_currentStartAngle];
self.endTime = [self endTimeWithAngle:_currentEndAngle];
if (self.delegate && [self.delegate respondsToSelector:@selector(alramViewIsChangedWithBeginTime:endTime:)]) {
[self.delegate alramViewIsChangedWithBeginTime:self.beginTime endTime:self.endTime];
}
return circlePath;
}
2.手势的处理类 DWRotaionGestureRecognizer
根据上面圆盘的效果,拖动手势时,我们需要知道起始点与拖动点之间的角度和方向。
核心代码:
/**拖动结束后自动重置 */
- (void)reset
{
[super reset];
// _previousRotation = [self rotation];
_previousRotation = 0;
_currentRotation = 0;
}
#pragma mark - eventResponse - Method -
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
self.startingPoint = [[touches anyObject] locationInView:self.view];
self.state = UIGestureRecognizerStateBegan;
if (self.rotaionGestureRecognizerDelegate && [self.rotaionGestureRecognizerDelegate respondsToSelector:@selector(touchesBegan:withEvent:)]) {
[self.rotaionGestureRecognizerDelegate touchesBegan:touches withEvent:event];
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
CGPoint point = [[touches anyObject] locationInView:self.view];
self.currentRotation = [UIView angleBetweenPoint1:self.startingPoint point2:point AndCenter:self.center];
self.state = UIGestureRecognizerStateChanged;
if (self.rotaionGestureRecognizerDelegate && [self.rotaionGestureRecognizerDelegate respondsToSelector:@selector(touchesMoved:withEvent:)]) {
[self.rotaionGestureRecognizerDelegate touchesMoved:touches withEvent:event];
}
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
self.endPoint = [[touches anyObject] locationInView:self.view];
self.currentRotation = [UIView angleBetweenPoint1:self.startingPoint point2:self.endPoint AndCenter:self.center];
self.state = UIGestureRecognizerStateEnded;
if (self.rotaionGestureRecognizerDelegate && [self.rotaionGestureRecognizerDelegate respondsToSelector:@selector(touchesEnded:withEvent:)]) {
[self.rotaionGestureRecognizerDelegate touchesEnded:touches withEvent:event];
}
}
- (void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
self.state = UIGestureRecognizerStateCancelled;
if (self.rotaionGestureRecognizerDelegate && [self.rotaionGestureRecognizerDelegate respondsToSelector:@selector(touchesCancelled:withEvent:)]) {
[self.rotaionGestureRecognizerDelegate touchesCancelled:touches withEvent:event];
}
}
计算两点间角度和距离的类别 UIView+DWAngle
核心代码:
/** 两个坐标点的角度 */
+ (CGFloat)angleBetweenPoint1:(CGPoint)first point2:(CGPoint)second AndCenter:(CGPoint)center{
// θ=arctan[(y2-y0)/(x2-x0)]-arctan[(y1-y0)/(x1-x0)];
CGPoint centeredPoint1 = CGPointMake(first.x - center.x, first.y - center.y);
CGPoint centeredPoint2 = CGPointMake(second.x - center.x, second.y - center.y);
CGFloat firstAngle = angleBetweenOriginAndPointA(centeredPoint1);
CGFloat secondAngle = angleBetweenOriginAndPointA(centeredPoint2);
CGFloat rads = secondAngle - firstAngle;
return rads;
}
/** 两点的距离 */
+(CGFloat)distanceBetweenPointA:(CGPoint)pointA AndPiontB:(CGPoint)pointB{
// (y2-y1)²+(x2-x1)²=d² sqrt() pow(5, 2)
CGFloat a = pow(pointB.x-pointA.x, 2);
CGFloat b = pow(pointB.y-pointA.y, 2);
return sqrt(a+b);
}
/** 某点和原点间的角度 */
+(CGFloat)angleBetweenOriginAndPointA:(CGPoint)p{
return angleBetweenOriginAndPointA(p);
}
CGFloat angleBetweenOriginAndPointA(CGPoint p) {
if (p.x == 0) {
return signA(p.y) * M_PI;
}
CGFloat angle = atan(-p.y / p.x); // '-' because negative ordinates are positive in UIKit
// atan() is defined in [-pi/2, pi/2], but we want a value in [0, 2*pi]
// so we deal with these special cases accordingly
switch (quadrantForPointA(p)) {
case 1:
case 2: angle += M_PI; break;
case 3: angle += 2* M_PI; break;
}
return angle;
}
/** 点的象限 */
NSInteger quadrantForPointA(CGPoint p) {
if (p.x > 0 && p.y < 0) {
return 0;
} else if (p.x < 0 && p.y < 0) {
return 1;
} else if (p.x < 0 && p.y > 0) {
return 2;
} else if (p.x > 0 && p.y > 0) {
return 3;
}
return 0;
}
NSInteger signA(CGFloat num) {
if (num == 0) {
return 0;
} else if (num > 0) {
return 1;
} else {
return -1;
}
}
以上是核心代码部分,感兴趣的读者可以到Github进行查阅:Github传送门
希望项目工程对您有所帮助,谢谢阅读。