選擇角度值的控件可以這樣實現:創建壹個圓形滑塊,用戶可以通過拖動手柄來選擇角度值。事實上,妳可能在其他平臺看到過這樣的控件,但在UIKit中沒有。
本文通過實現壹個用於選擇角度值的控件來介紹控件的自定義。讓我們來看看它是什麽樣子的:
1.UIControl子類
UIControl是UIView的子類,是所有UIKit控件(如UIButton、UISlider、UISwitch)的父類。
UIControl的主要功能是創建相應的邏輯,將動作分發到相應的目標。在另外90%的情況下,它會根據自己的狀態(比如高亮、選中、禁用)來繪制用戶界面。
通過UIControl,我們主要管理三項重要任務:
繪制用戶界面
跟蹤用戶的操作
目標行動模式
在本文的圓形滑塊中,我們必須做以下事情:
自定義壹個用戶界面(圓形滑塊本身),通過該界面,用戶可以通過手柄進行交互。用戶的交互將被轉換為控件目標對應的動作(控件將滑塊按鈕的幀原點轉換為0到360之間的值,並在目標/動作上使用)。
學習本文時,建議從文末的鏈接下載完整的示例項目。
下面我將逐壹介紹上面列出的三項重要任務。
這些步驟是模塊化的,所以如果妳對繪制界面不感興趣,可以跳過繪制用戶界面,直接學習下面的步驟。
打開項目文件中的TBCircluarSlider.m文件。然後開始學下面的。
1.1繪圖用戶界面
我更喜歡使用核心圖形,使用UIKit的唯壹方法是通過textfield顯示滑塊的值。
提醒:這裏需要壹些核心圖形的知識。不懂也沒關系。我會盡力詳細解釋代碼。
我們先來看看控件的不同組件,這樣更有利於後面的學習。
首先,壹個黑色的圓環被用作滑塊的背景。
活動區域是從藍色到紫色的漸變效果。
用戶通過拖放以下手柄按鈕來選擇壹個值:
最後,用於顯示所選值的TextField。在下壹個版本中,我計劃讓用戶通過鍵盤輸入角度值。
控制界面的繪制主要使用drawRect函數。首先,我們需要獲取當前使用的圖形上下文,如下面的代碼所示:
1
CGContextRef CTX = UIGraphicsGetCurrentContext();
1.1.1繪制背景。
背景是360°的,只需使用CGContextAddArc將正確的路徑添加到圖形上下文中,並設置正確的筆畫即可。
下面的代碼可以完成背景的繪制:
//添加圓弧路徑
CGContextAddArc(ctx,self.frame.size.width/2, self.frame.size.height/2,半徑,0,M_PI *2,0);
//設置筆畫顏色
[[ui color black color]set stroke];
//設置線寬和線帽
CGContextSetLineWidth(ctx,TB _ BACKGROUND _ WIDTH);
CGContextSetLineCap(ctx,kCGLineCapButt);
//畫出來!
CGContextDrawPath(ctx,kCGPathStroke);
CGContextArc函數的參數包括圖形上下文,弧度的中心坐標點,半徑(這是壹個私有變量),後面是弧度開始和結束的角度(壹些數學計算方法可以在TBCircularSlider.m文件頭看到),最後壹個參數表示繪制方向,0表示逆時針方向。
接下來的三行代碼用於設置壹些信息,比如顏色和線寬。最後使用CGContextDrawPath方法繪制背景。
1.1.2畫出用戶的可操作區域。
這部分需要壹點技巧。這裏我們畫壹個線性漸變的蒙版圖。讓我們來看看原理:
這裏蒙版圖像的工作原理是,妳可以在原來的漸變矩形框裏看到壹個洞。
這裏畫的弧度有陰影,是在創建蒙版貼圖的時候用了壹點模糊的效果。
以下是創建遮罩貼圖的相關代碼:
UIGraphicsBeginImageContext(CGSizeMake(320,320));
CGContextRef imageCtx = UIGraphicsGetCurrentContext();
CGContextAddArc(imageCtx,self.frame.size.width/2,self.frame.size.height/2,半徑,0,ToRad(self.angle),0);
[[ui color red color]set];
//使用陰影創建模糊效果
CGContextSetShadowWithColor(imageCtx,CGSizeMake(0,0),self.angle/20,[ui color black color]。CG color);
//定義路徑
CGContextSetLineWidth(imageCtx,TB _ LINE _ WIDTH);
CGContextDrawPath(imageCtx,kCGPathStroke);
//將上下文內容保存到圖像掩碼中
CGImageRef mask = CGBitmapContextCreateImage(UIGraphicsGetCurrentContext());
UIGraphicsEndImageContext();
在上面的代碼中,首先創建壹個圖形上下文,然後設置壹個陰影。通過CGContextSetShadowWithColor方法,我們可以設置以下內容:
語境
偏移(此處不需要)
模糊值(該值由參數控制:將當前角度除以20,得到用戶與該控件交互時的簡單動畫模糊值)。
顏色
然後根據當前角度畫出相應的弧度。
比如當前角度變量為360,畫壹個圓弧;如果是90,畫壹個弧度為90的圓弧。最後用CGBitmapContextCreateImage方法得到壹張圖片(剛剛畫的弧)。這張圖就是我們需要的掩膜圖。
剪輯上下文:
現在我們有了壹個漸變遮罩圖。然後使用函數CGContextClipToMask來剪切上下文,將剛剛創建的掩碼映射傳遞給這個函數。代碼如下:
CGContextClipToMask(ctx,self.bounds,mask);
最後,我們來畫壹個漸變效果。代碼如下:
//定義顏色步驟
CGFloat組件[8] = {
0.0,0.0,1.0,1.0,//開始顏色-藍色
1.0, 0.0, 1.0, 1.0 };//結束顏色-紫色
cgcolorspace ref base space = cgcolorspace createdevicergb();
CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace,Components,NULL,2);
//定義漸變方向
CG point start point = CGPointMake(CGRectGetMidX(rect),CGRectGetMinY(rect));
CG point endPoint = CGPointMake(CGRectGetMidX(rect),CGRectGetMaxY(rect));
//選擇壹個色彩空間
CGColorSpaceRelease(baseSpace),baseSpace = NULL
//創建並繪制漸變
cgcontextdrawlaneGradient(CTX,gradient,startPoint,endPoint,0);
CGGradientRelease(gradient),gradient = NULL
繪制漸變效果需要大量的處理,但我們可以將其分為四個部分:
定義顏色變化的範圍。
定義漸變的方向。
選擇色彩空間
創建並繪制漸變
最終的顯示效果(見漸變矩形框的壹部分)歸功於之前創建的掩膜圖。
另外,為了模擬背景邊框的光線反射,我添加了壹些燈光效果。
1.1.3繪圖手柄
讓我們根據當前的角度值在正確的位置繪制手柄。
其實在畫圖的過程中,這壹步很簡單,比較復雜的是計算手柄的位置。
這裏我們需要用三角函數將壹個標量值(標量數)轉換成CGPoint。別管有多復雜,用Sin和Cos函數就行了。代碼如下:
-(CG point)pointfroangle:(int)angle int {
//定義圓心
CG point center point = CGPointMake(self . frame . size . WIDTH/2-TB _ LINE _ WIDTH/2,self.frame.size.height/2-TB _ LINE _ WIDTH/2);
//定義圓周上的點位置
CGPoint結果;
result . y = round(center point . y+radius * sin(ToRad(-angle int)));
result . x = round(center point . x+radius * cos(ToRad(-angle int)));
返回結果;
}
在上面的代碼中,指定壹個角度值,然後計算圓周上方的位置。當然這裏需要圓周的中心點和半徑。
使用sin函數時,需要壹個Y坐標值,而cos函數需要壹個X坐標值。
需要註意的是,這裏每個函數返回的值都被認為是1的半徑,所以我們需要將結果乘以我們指定的半徑,相對於圓心進行計算。
希望下面的公式能幫助妳理解:
1
2
point.y = center.y +(半徑* sin(角度));
point.x = center.x +(半徑* cos(角度));
通過上面的計算,現在我們已經知道了句柄的具體位置,所以我們可以直接將句柄繪制到指定的位置,如下面的代碼所示:
-(void)drawTheHandle:(CGContextRef)CTX {
CGContextSaveGState(CTX);
//我喜歡影子
CGContextSetShadowWithColor(CTX,CGSizeMake(0,0),3,[UIColor blackColor]。CG color);
//獲取句柄位置!
CG point handle center =[self point from angle:self . angle];
//畫出來!
[[ui color colorWithWhite:1.0 alpha:0.7]set];
cgcontextfilllellipseinrect(CTX,CGRectMake(handleCenter.x,handleCenter.y,TB_LINE_WIDTH,TB _ LINE _ WIDTH));
CGContextRestoreGState(CTX);
}
具體操作步驟如下:
保存當前上下文(在單獨的函數中繪圖時保存上下文的狀態是壹個好習慣)。
在手柄上設置壹些陰影效果。
定義手柄的顏色,然後用CGContextFillEllipseInRect繪制。
我們在drawRect函數的末尾調用上面的方法:
1
【自畫the handle:CTX】;
至此,我們已經完成了繪制零件的任務。
1.2跟蹤用戶的操作
在UIControl的子類中,我們可以覆蓋三個特殊的方法來提供自定義的跟蹤行為。
1.2.1開始跟蹤。
當在控件的邊界內發生觸摸事件時,首先調用控件的beginTrackingWithTouch方法。
讓我們看看如何覆蓋這個方法:
-(BOOL)beginTrackingWithTouch:(ui touch *)touch with event:(ui event *)event {
[super beginTrackingWithTouch:touch withEvent:event];
//我們需要持續跟蹤
返回YES
}
該函數返回的布爾值決定了當觸摸事件被拖動時是否需要響應。在這裏的自定義控件中,我們需要跟蹤用戶的拖動,所以我們返回YES。
上面的函數有兩個參數:觸摸對象和事件。
1.2.2連續跟蹤
在前面的方法中,我們指定這裏的自定義控件需要跟蹤壹個持久事件,所以當用戶拖動時,將調用壹個特殊的方法:continueTrackingWithTouch:
-(BOOL)continueTrackingWithTouch:(ui touch *)touch with event:(ui event *)事件
此方法返回的布爾值指示是否繼續跟蹤觸摸事件。
通過這個方法,我們可以根據觸摸位置來過濾用戶的操作。例如,只有當觸摸位置與手柄位置相交時,我們才能激活控制。但是,我們這裏的控制邏輯不是這樣的。我們希望用戶可以單擊任何位置來相應地處理手柄。
本文中的這個方法負責更新句柄的位置(我們將在後面的小節中看到我們將位置信息傳遞給相應的目標)。
上述方法的覆蓋代碼如下:
-(BOOL)continueTrackingWithTouch:(ui touch *)touch with event:(ui event *)event {
[super continueTrackingWithTouch:touch withEvent:event];
//獲取觸摸位置
CG point last point =[touch location in view:self];
//使用位置來設計句柄
[self move handle:last point];
//我們將在下壹節看到這個函數:
[self sendActionsForControlEvents:UIControlEventValueChanged];
返回YES
}
在上面的代碼中,locationInView用於獲取觸摸的位置,然後將該位置傳遞給moveHandle方法,該方法會將傳入的值轉換為有效的句柄位置。
這裏的“有效位置”是什麽意思?
這個控件的手柄只能在背景弧線定義的邊界內移動,但是我們不想強迫用戶在壹個小弧線內移動手柄。如果這樣的話,用戶體驗會很糟糕。
MoveHandle的任務是將任意位置值轉換成句柄的可移動值。另外,在這個函數中,指定的滑塊角度值也被轉換,代碼如下:
-(void)move handle:(CG point)last point {
//獲取中心
CGPoint center point = CGPointMake(self . frame . size . width/2,
self . frame . size . height/2);
//計算從中心點到任意位置的方向。
float current tangle = angle from north(center point,
最後壹點,
否);
int angle int = floor(current tangle);
//存儲新角度
self . angle = 360-angle int;
//更新文本字段
_ textfield . text =[ns string string with format:@ " % d ",
self . angle];
//重繪
[self set needs display];
}
在上面的代碼中,主要的任務實際上是在AngleFromNorth方法中處理的:根據兩點,會返回壹個連接兩點的角度關系,AngleFromNorth方法的實現如下:
靜態內嵌浮點AngleFromNorth(CGPoint p1,CGPoint p2,布爾值翻轉){
CG point v = CGPointMake(p2 . x-p 1 . x,p2 . y-p 1 . y);
float vmag = sqrt(SQR(v . x)+SQR(v . y)),結果= 0;
v . x/= vmag;
v . y/= vmag;
雙弧度= atan2(v.y,v . x);
result = ToDeg(弧度);
return(結果& gt=0 ?結果:結果+360.0);
}
提醒:angleFromNorth方法不是我獨創的。我直接從蘋果公司提供的OSX樣品時鐘控制。
在上面的代碼中,獲取角度值後,將其存儲在angle中,然後更新textfield的值。
接下來調用setNeedDisplay是為了確保調用drawRect,以便盡快更新界面。
1.2.3末端跟蹤
當跟蹤結束時,將調用以下方法:
-(void)endTrackingWithTouch:(ui touch *)touch with event:(ui event *)event {
[super endTrackingWithTouch:touch withEvent:event];
}
在本文中,我們不需要覆蓋這個方法。如果妳想在用戶完成控件的界面操作時做壹些處理,這個方法非常有用。
1.3目標-行動模式
此時,圓形滑塊控件可以工作了。您可以拖移控制柄,查看文本字段中值的變化。
發送動作控制事件
如果希望自定義的控件與UIControl的行為壹致,當控件的值發生變化時,需要通知它:使用sendActionsForControlEvents方法,並做壹個特定的事件類型。值更改對應的事件壹般為UIControlEventValueChanged。
蘋果預定義了很多事件類型(在Xcode中,cmd+鼠標點擊UIControlEventValueChanged)。如果您的控件繼承自UITextField,您可能會對UIControlEventDigitingDidBegin感興趣。如果妳想做壹個修飾動作,妳可以使用UIControlTouchUpInside。
如果您註意到,在本文前壹部分的continueTrackingWithTouch方法中,我們將sendActionsForControlEvents方法稱為:
[self sendActionsForControlEvents:UIControlEventValueChanged];
在這個處理之後,當控制值改變時,每個對象(觀察者-註冊事件)將被通知響應。