最近看了蘋果自帶應(yīng)用時鐘上的時間選擇工具感覺挺巧妙的靡菇,就嘗試著模仿它做出一個控件工具佳晶。工程Demo運(yùn)行效果如下:
根據(jù)時鐘選擇工具上面的功能送讲,大概可以確定济竹,圓環(huán)的繪制我們可以通過CAShapeLayer
結(jié)合UIBezierPath
繪制出來,當(dāng)拖動起始點(diǎn)或者結(jié)束點(diǎn)View
時冤寿,通過手勢判斷拖動的角度歹苦,從而改變UIBezierPath
的角度,并且讓起始點(diǎn)或者結(jié)束點(diǎn)View
根據(jù)拖動的角度對中心點(diǎn)進(jìn)行公轉(zhuǎn)督怜,并且自身進(jìn)行自轉(zhuǎn)暂氯。如果拖動圓環(huán)的話,改變UIBezierPath
角度的同時亮蛔,還要讓起始點(diǎn)和結(jié)束點(diǎn)View
同時進(jìn)行公轉(zhuǎn)和自轉(zhuǎn)。
1.時鐘AlarmView
時鐘View是該控件的核心區(qū)擎厢,里面包含了圖形的繪制和旋轉(zhuǎn)究流,旋轉(zhuǎn)類型的判斷等多種處理。
核心代碼:
-(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) {//修復(fù)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));//公轉(zhuǎn)
self.sleepView.transform = CGAffineTransformMakeRotation(-kDgreesToRadoans(self.startAngle+startAngle));//自轉(zhuǎn)
self.alramLayer.path = [self drawAlarmPathWithStartAngle:startAngle+self.startAngle endAngle:self.endAngle].CGPath;
}
/** 改變結(jié)束時間 */
-(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;
}
/** 改變圓環(huán)位置 */
-(void)changeCircularingLocation:(CGFloat)angle{
self.sleepSuperView.transform = CGAffineTransformMakeRotation(kDgreesToRadoans(self.startAngle+angle));//公轉(zhuǎn)
self.sleepView.transform = CGAffineTransformMakeRotation(-kDgreesToRadoans(self.startAngle+angle));//自轉(zhuǎn)
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;
}
/** 旋轉(zhuǎn)類型 */
-(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
根據(jù)上面圓盤的效果动遭,拖動手勢時芬探,我們需要知道起始點(diǎn)與拖動點(diǎn)之間的角度和方向。
核心代碼:
/**拖動結(jié)束后自動重置 */
- (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];
}
}
計算兩點(diǎn)間角度和距離的類別 UIView+DWAngle
核心代碼:
/** 兩個坐標(biāo)點(diǎn)的角度 */
+ (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;
}
/** 兩點(diǎn)的距離 */
+(CGFloat)distanceBetweenPointA:(CGPoint)pointA AndPiontB:(CGPoint)pointB{
// (y2-y1)2+(x2-x1)2=d2 sqrt(<#double#>) pow(5, 2)
CGFloat a = pow(pointB.x-pointA.x, 2);
CGFloat b = pow(pointB.y-pointA.y, 2);
return sqrt(a+b);
}
/** 某點(diǎn)和原點(diǎn)間的角度 */
+(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;
}
/** 點(diǎn)的象限 */
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進(jìn)行查閱:Github傳送門
希望項目工程對您有所幫助偷仿,謝謝閱讀哩簿。