前段時間和群裡的一個設計師配合,根據網上的一個旋鈕gif為原型,用代碼做出來了。先來看下原型的效果
在看下下面是我做完後成品的效果,加了一個通過移動光源來對陰影控制的效果,這樣看起來更立體點。(那個太陽有點不協調。。。)
同時附上代碼鏈接https://git.oschina.net/BearXR/CircleKnob
還有單獨的下載包http://download.csdn.net/detail/xiongbaoxr/9419655
下面開始簡單的講解下構思步驟和部分代碼片段
1,構建底盤
首先我們要構造兩個圓盤和旋鈕上的紅點
用view畫兩個圓盤,和一個小的紅色控制點。
注意:紅色的控制點我又單獨新建了一個view,這樣方便待會做手勢控制,從而不會影響到後面的view。而且所有view的圓心都要設置好。後面和角度相關的處理會很多的。最好能准確和self.view或者window能有直接的關聯。
2,底盤增加點擊和拖動的手勢
在這裡我們要增加手勢,還有起始點,終點的角度。我畫了輔助線,便於調試。手勢和過渡效果都寫好了。所以效果我就直接放上來了。
3,增加外圍的扇環
由於後面要做陰影,所以除了扇環的底色和效果是用了一個view,其余的每個小格子我都是新開了一個view,並且將旋鈕的圓心位置設置為錨點進行旋轉。
#pragma mark - 設置外圍的扇環形 - (void)initSetFanView { CGFloat delta_distance = 26; fanView = [[FanView alloc] initWithFrame:CGRectMake(0, 0, knob_width + delta_distance * 2, knob_width + delta_distance * 2)]; fanView.center = knob.center; fanView.backgroundColor = [UIColor clearColor]; fanView.userInteractionEnabled = NO; [self.view addSubview:fanView]; fanView.knobValue = -startAngleValue;//設置起始點 fanView.lightSource_InWindow = lightSource; } - (void)drawRect:(CGRect)rect { contextBack = UIGraphicsGetCurrentContext(); contextFore = UIGraphicsGetCurrentContext(); [self drawFan:contextBack bezierPath:bezierPathBack knobAngle:180 + endAngleValue strokeColor:[UIColor colorWithRed:202/255.0 green:207/255.0 blue:202/255.0 alpha:1.0f]]; [self drawFan:contextFore bezierPath:bezierPathFore knobAngle:_knobValue strokeColor:[UIColor colorWithRed:174/255.0 green:0/255.0 blue:0/255.0 alpha:1.0f]]; } // 繪制扇形 - (void)drawFan:(CGContextRef)context bezierPath:(UIBezierPath *)bezierPath knobAngle:(CGFloat)knobAngle strokeColor:(UIColor *)strokeColor { CGRect frame = self.frame; CGFloat radius = (CGRectGetWidth(frame) - lineWidth) / 2; CGFloat angleForOne = M_PI / 180.0f; CGFloat circleLength = radius * 2 * M_PI; int gapCount = fanShowCount - 1; //間隙個數 CGFloat gapWidth = 5; //間隙距離 // 計算需要繪制的角度(角度制) knobAngle = knobAngle < -startAngleValue ? -startAngleValue : knobAngle; knobAngle = knobAngle > 180 + endAngleValue ? 180 + endAngleValue : knobAngle; // 設置弧線路徑 bezierPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(CGRectGetWidth(frame)/2, CGRectGetHeight(frame)/2) radius:(CGRectGetWidth(frame) - lineWidth)/2.0 startAngle:angleForOne * (180 - startAngleValue) endAngle:angleForOne * (180 + knobAngle) clockwise:YES]; CGContextAddPath(context, bezierPath.CGPath); // 設置線的顏色,線寬,接頭樣式 CGContextSetStrokeColorWithColor(context, strokeColor.CGColor); CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor); CGContextSetLineWidth(context, lineWidth); CGContextSetLineCap(context, kCGLineCapButt); // 繪制虛線 CGFloat drawLineLength = circleLength * (1- (startAngleValue + endAngleValue)/fullAngleValue); CGFloat showLineLengthPer = (drawLineLength - gapWidth * gapCount)/(fanShowCount - 1); CGFloat lengths[2] = {showLineLengthPer,gapWidth}; CGContextSetLineDash(context, 0, lengths, 2); CGContextDrawPath(context, kCGPathFillStroke);//最後一個參數是填充類型 if (!self.blockViewArray) { self.blockViewArray = [[NSMutableArray alloc] init]; } // 繪制小方格view(並且只繪制一次) static BOOL drawBlock = NO; if (!drawBlock) { drawBlock = YES; for (int i = 1; i < fanShowCount; i++) { CGFloat blockWidth = lineWidth + 8; CGFloat blockHeight = 5; CGFloat block_x = CGRectGetWidth(frame) / 2 - radius - blockWidth/2; CGFloat block_y = CGRectGetHeight(frame) / 2; // 角度修正 if (blockHeight > gapWidth) { block_y = block_y - blockHeight/2; }else{ block_y = block_y + (gapWidth - blockHeight)/2; } // 方格view 可輔助繪制垂直平分線 ViewWithAutoShadow *viewBlock = [[ViewWithAutoShadow alloc] initWithFrame:CGRectMake(block_x, block_y, blockWidth, blockHeight)]; viewBlock.showAssistPoint = NO; viewBlock.backgroundColor = [UIColor colorWithRed:248/255.0 green:238/255.0 blue:237/255.0 alpha:1.0f]; [self addSubview:viewBlock]; // 根據錨點旋轉 CGFloat blockAngle = (180 + startAngleValue + endAngleValue)/fanShowCount*i - startAngleValue; CGAffineTransform rotate = GetCGAffineTransformRotateAroundPoint1(viewBlock.center.x, viewBlock.center.y, CGRectGetWidth(frame)/2, CGRectGetHeight(frame)/2, blockAngle/180.0 * M_PI); [viewBlock setTransform:rotate]; AppDelegate *myDelegate = [[UIApplication sharedApplication] delegate]; [viewBlock drawShadowEffectWithSourcePoint:_lightSource_InWindow assistInView:myDelegate.window]; [self.blockViewArray addObject:viewBlock]; } } }
4,增加動畫效果
給底盤和扇環都增加動效,裡面用了定時器。關於定時器可以看參考這篇博客http://blog.csdn.net/xiongbaoxr/article/details/50580701
動效的代碼片段
// 執行動畫 - (void)changeRadiusWithAnimation:(CGFloat)radius lastRadius:(CGFloat)lastRadius duration:(CGFloat)duration { CGFloat countAll = ABS(radius - lastRadius); double delaySeconds = 0.001f; CGFloat animateCount = duration/delaySeconds; __block int i = 0; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, delaySeconds * NSEC_PER_SEC, 0); dispatch_source_set_event_handler(timer, ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ if (i >= animateCount) { dispatch_source_cancel(timer); }else{ i ++; dispatch_sync(dispatch_get_main_queue(), ^{ // 扇環進度條動畫 CGFloat anglePer = countAll/animateCount * i; int k = radius > lastRadius ? anglePer : -anglePer; CGFloat needValue = lastRadius + k; fanView.knobValue = needValue; // 該方法會重新調用drawRect方法 [fanView setNeedsDisplay]; // 旋鈕轉動 knob.transform = CGAffineTransformMakeRotation((needValue/180.0 - 0) * M_PI); }); } }); }); dispatch_resume(timer); }
5,最後一步是處理陰影效果,裡面涉及的東西比較多。不詳細解釋代碼,說一下思路。關於陰影處理的主要是這個類 ViewWithAutoShadow.h
5.1,首先我們設置一個光源點,這個小太陽就是我們的光源所在的位置
5.2,為了便於調試和理解,我對所有需要做投影效果的view畫出了其與光源的連線,view的垂直平分線。並且進行了相應的延長。
同時也把view的四個頂點和中心點也描出來了
5.3,注意看,每個夾腳處都有一個小的出頭部分,這就是陰影的偏移量。按照這個偏移量來設置陰影就會有一種仿真的效果。
5.4,我們把陰影效果設置上去看下最終的效果,是不是開始有點感覺了?
5.5最後把所有的輔助線都關掉,再看看效果