iOS自定義轉(zhuǎn)盤控件

系統(tǒng)的控件有UIButton募强、UISlider、UITextField等崇摄,它們共有的特點是能夠響應(yīng)觸摸事件擎值,并作出相應(yīng)的變化。

這些系統(tǒng)控件都繼承自UIControl逐抑,顧名思義鸠儿,UIControl能夠處理各種觸摸事件,它的頭文件中列出了所有能夠處理的事件厕氨。

創(chuàng)建自定義控件的基本思路是:先在-drawRect:方法中畫出Normal狀態(tài)下的樣子进每,用戶觸發(fā)觸摸事件時,根據(jù)觸摸事件畫出相應(yīng)的圖形命斧,這時控件的樣子會隨觸摸事件改變田晚,調(diào)用-sendActionsForControlEvents:方法向外部發(fā)送觸摸事件。

現(xiàn)在制作一個如下圖的自定義控件国葬。

自定義控件.png

一肉瓦、繼承UIControl遭京,重寫方法

UIControl是抽象類,只提供API接口泞莉,并不能直接使用哪雕。所以先創(chuàng)建一個類,繼承自UIControl鲫趁,然后重寫它的一些方法:

- (void)drawRect:(CGRect)rect;
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event;
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event;
- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event;
- (void)cancelTrackingWithEvent:(UIEvent *)event;

-drawRect:方法中繪制控件斯嚎,控件就是這個方法畫出來的
-beginTrackingWithTouch:withEvent:是監(jiān)測到觸摸事件并開始追蹤
-continueTrackingWithTouch:withEvent:是持續(xù)監(jiān)測觸摸事件
-endTrackingWithTouch:withEvent:是監(jiān)測結(jié)束
-cancelTrackingWithEvent:是監(jiān)測到取消觸摸事件

-drawRect:方法負責(zé)圖形顯示,另外三個方法負責(zé)交互挨厚。交互的方法被用戶觸發(fā)之后堡僻,調(diào)用-setNeedsDisplay來間接調(diào)用-drawRect:方法進行繪圖。注意不要直接手動調(diào)用drawRect疫剃,要通過setNeedsDisplay方法讓系統(tǒng)異步調(diào)用drawRect钉疫,否則可能會發(fā)生錯誤。

二巢价、在-drawRect:中繪圖

控件的最底部是三個白色半透明的圓弧牲阁,上面是白色不透明的圓弧,每個圓弧上有前后兩個標簽壤躲,白色不透明圓弧隨標簽的滑動而改變城菊,標簽外側(cè)是說明文字。其實還是蠻好畫的碉克,不算很復(fù)雜凌唬。

- (void)drawRect:(CGRect)rect {
    for (NSInteger i = 0; i < _sectors.count; i ++) {
        // 取出三個弧形區(qū)域?qū)ο?        Sector *sector = [_sectors objectAtIndex:i];
    
        CGContextRef context = UIGraphicsGetCurrentContext();
        // 畫圓弧
        CGContextSetLineWidth(context, 25);
    
        UIColor *bottomColor = [UIColor colorWithWhite:1 alpha:0.5];
        UIColor *topColor = [UIColor colorWithWhite:1 alpha:1];
    
        [bottomColor setStroke];
        CGContextAddArc(context, sector.centerPoint.x, sector.centerPoint.y, Circle_Width, sector.startAngle, sector.startAngle+0.6*M_PI, 0);
        CGContextStrokePath(context);
    
        [topColor setStroke];
        CGContextAddArc(context, sector.centerPoint.x, sector.centerPoint.y, Circle_Width, sector.beginAngle, sector.endAngle, 0);
        CGContextStrokePath(context);
    
        // 畫直線
        [sector.color setStroke];
        CGContextSetLineWidth(context, 2.0);
    
        CGPoint beginSmallPoint = polarToDecart(sector.centerPoint, Circle_Width-15, sector.beginAngle);
        CGPoint beginBigPoint = polarToDecart(sector.centerPoint, Circle_Width+15, sector.beginAngle);
    
        CGContextMoveToPoint(context, beginSmallPoint.x, beginSmallPoint.y);
        CGContextAddLineToPoint(context, beginBigPoint.x, beginBigPoint.y);
        CGContextStrokePath(context);
    
        CGPoint endSmallPoint = polarToDecart(sector.centerPoint, Circle_Width-15, sector.endAngle);
        CGPoint endBigPoint = polarToDecart(sector.centerPoint, Circle_Width+15, sector.endAngle);
    
        CGContextMoveToPoint(context, endSmallPoint.x, endSmallPoint.y);
        CGContextAddLineToPoint(context, endBigPoint.x, endBigPoint.y);
        CGContextStrokePath(context);
    
        // 畫扇形
        CGContextSetLineWidth(context, 2.0);
        [sector.color setStroke];
        [sector.color setFill];
        CGContextMoveToPoint(context, beginBigPoint.x, beginBigPoint.y);
        CGContextAddArc(context, beginBigPoint.x, beginBigPoint.y, 10, sector.beginAngle-0.2*M_PI, sector.beginAngle+0.2*M_PI, 0);
        CGContextClosePath(context);
        CGContextDrawPath(context, kCGPathFillStroke);
    
        CGContextMoveToPoint(context, endBigPoint.x, endBigPoint.y);
        CGContextAddArc(context, endBigPoint.x, endBigPoint.y, 10, sector.endAngle-0.2*M_PI, sector.endAngle+0.2*M_PI, 0);
        CGContextClosePath(context);
        CGContextDrawPath(context, kCGPathFillStroke);
    
        NSString *startString = @"end";
        NSString *endString   = @"start";

        // 直角坐標轉(zhuǎn)極坐標
        PolarCoordinate strBeginPolar = decartToPolar(sector.centerPoint, sector.beginPoint);
        // 極坐標轉(zhuǎn)直角坐標
        CGPoint strBeginPoint = polarToDecart(sector.centerPoint, strBeginPolar.radius+50, strBeginPolar.angle);
    
        PolarCoordinate strEndPolar = decartToPolar(sector.centerPoint, sector.endPoint);
        CGPoint strEndPoint = polarToDecart(sector.centerPoint, strEndPolar.radius+50, strEndPolar.angle);
    
        // 畫文字
        [self drawString:startString
                withFont:[UIFont systemFontOfSize:17]
                andColor:[UIColor whiteColor]
               andCenter:strBeginPoint
                  andTag:sector.tag
                  radius:strBeginPolar.radius+50];
    
        [self drawString:endString
                withFont:[UIFont systemFontOfSize:17]
                andColor:[UIColor whiteColor]
               andCenter:strEndPoint
                  andTag:sector.tag
                  radius:strEndPolar.radius+50];
    }
}

這樣控件就畫好了,對象初始化之后會自動調(diào)用一次drawRect繪制出來漏麦。

三客税、處理觸摸事件

控件畫出來了,接下來就要處理觸摸事件撕贞,這個自定義控件的觸摸事件類似UISlider更耻,是滑動式的事件。

控件有三個主要區(qū)域麻掸,每個區(qū)域有兩個滑動標簽酥夭,只要捕獲到用戶觸摸區(qū)域在滑動標簽周圍赐纱,就說明用戶要開始滑動標簽了脊奋,然后讓標簽跟隨用戶的觸摸點移動,這個自定義控件就“活”了疙描。

1.如何知道用戶的觸摸點是否在滑動標簽周圍呢诚隙?

- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event方法最先捕獲到用戶觸摸事件,touch對象包含了觸摸區(qū)域起胰,通過[touch locationInView:self]方法可以獲取觸摸點的坐標久又。

簡單的自定義控件可以通過CGRectContainsPoint(rect, point)判斷point是否在rect內(nèi)巫延,但這個控件就不行,因為兩個區(qū)域之間的距離太近地消,標簽的frame會有部分區(qū)域重合炉峰。這里我使用的方法是創(chuàng)建一個梯形貝塞爾路徑,正好把標簽包含住而又不重合脉执,當然代碼也多了不少疼阔。效果如下,是不是很贊半夷?

標簽區(qū)域.png

使用方法[beginPath containsPoint:touchPoint];來判斷觸摸區(qū)域是否在滑動標簽周圍婆廊,是的話就返回YES繼續(xù)處理,否則返回NO終止處理巫橄。

2.繼續(xù)處理觸摸事件

- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event是繼續(xù)處理的方法淘邻。

用戶觸摸的位置是任意的,但我們的控件卻是圓的湘换,所以這里就需要一個函數(shù)把觸摸點所在的直角坐標轉(zhuǎn)換成極坐標宾舅,這樣就能得到觸摸點的角度,得到角度之后立即繪制該角度對應(yīng)的圖像枚尼。比如滑動標簽1原始位置是0度贴浙,用戶觸摸的位置是20度,就立即調(diào)用setNeedsDisplay方法把滑動標簽1繪制到20度的位置署恍。

- continueTrackingWithTouch:withEvent:方法中崎溃,用戶滑動的坐標基本是連續(xù)的,我們不斷地調(diào)用setNeedsDisplay盯质,系統(tǒng)就不斷地重繪袁串,這就產(chǎn)生了用戶滑到哪里,滑動標簽就跟隨到哪里的效果呼巷。

手動調(diào)用[self sendActionsForControlEvents:UIControlEventValueChanged];方法囱修,就能在每次改變值之后向外發(fā)通知,我們在外部就能通過[custom addTarget:self action:@selector(sectorValueChanged:) forControlEvents:UIControlEventValueChanged];來實時獲取值了王悍。

3.觸摸事件結(jié)束

- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event表示用戶離開屏幕破镰,不再進行觸摸。

到這里压储,自定義控件基本就創(chuàng)建好了鲜漩,當然創(chuàng)建自定義控件是要使用的,所以得對外暴露一些接口集惋,比如設(shè)置顏色孕似、角度、數(shù)值等刮刑。


結(jié)束了嗎喉祭?還沒有养渴。


這種直接繪制的方法太粗暴,下面用一種優(yōu)雅的方式創(chuàng)建它泛烙。

四理卑、優(yōu)雅地創(chuàng)建自定義控件

最好的方法是仿照蘋果,將顯示與交互分開蔽氨,CALayer負責(zé)圖形顯示傻工,控件本身負責(zé)交互。重繪代碼分離到CALayer中孵滞,交互代碼留在UIControl中中捆,代碼結(jié)構(gòu)清晰、邏輯明了坊饶。

這個自定義控件的三個區(qū)域其實是一個樣子的泄伪,只是位置和顏色不同,上面的方法是一次性繪制三個區(qū)域匿级,代碼比較臃腫蟋滴,如果使用CALayer繪制一個區(qū)域的Layer,使用的時候創(chuàng)建三個分別放在不同位置痘绎,這樣就簡單多了津函。

1.創(chuàng)建UIControl子類,重寫交互方法

與上面一樣孤页,先創(chuàng)建一個類繼承于UIControl尔苦,重寫begin/continue/end/cancel四個方法,主要在begin和continue中處理觸摸邏輯與數(shù)據(jù)計算行施,有些復(fù)雜的控件這里的判斷代碼可能寫起來比較困難允坚。

2.創(chuàng)建Layer,繪制控件

continue方法中蛾号,數(shù)據(jù)處理完畢之后手動調(diào)用layer的-setNeedsDisplay方法進行重繪稠项,重繪的代碼與上面類似。CALayer中的重繪方法與UIView不同鲜结,它是在- (void)drawInContext:(CGContextRef)ctx中進行的展运。

要注意的是,重繪需要的所有參數(shù)都是由自定義控件傳過來的精刷,比如半徑拗胜、顏色、寬度等贬养,正是這些數(shù)據(jù)將兩個類關(guān)聯(lián)了起來挤土,有必要重寫這些參數(shù)的setter方法琴庵,發(fā)現(xiàn)修改任意參數(shù)的時候就進行重繪误算。

自定義控件就寫這么多吧仰美,具體的源碼請參考我的GitHub

zdykj.gif
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末儿礼,一起剝皮案震驚了整個濱河市咖杂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蚊夫,老刑警劉巖诉字,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異知纷,居然都是意外死亡壤圃,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門琅轧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伍绳,“玉大人,你說我怎么就攤上這事乍桂〕迳保” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵睹酌,是天一觀的道長权谁。 經(jīng)常有香客問我,道長憋沿,這世上最難降的妖魔是什么旺芽? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮辐啄,結(jié)果婚禮上甥绿,老公的妹妹穿的比我還像新娘。我一直安慰自己则披,他們只是感情好共缕,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著士复,像睡著了一般图谷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上阱洪,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天便贵,我揣著相機與錄音,去河邊找鬼冗荸。 笑死承璃,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蚌本。 我是一名探鬼主播盔粹,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼隘梨,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了舷嗡?” 一聲冷哼從身側(cè)響起轴猎,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎进萄,沒想到半個月后捻脖,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡中鼠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年可婶,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片援雇。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡扰肌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出熊杨,到底是詐尸還是另有隱情曙旭,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布晶府,位于F島的核電站桂躏,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏川陆。R本人自食惡果不足惜剂习,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望较沪。 院中可真熱鬧鳞绕,春花似錦、人聲如沸尸曼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽控轿。三九已至冤竹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間茬射,已是汗流浹背鹦蠕。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留在抛,地道東北人钟病。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親肠阱。 傳聞我的和親對象是個殘疾皇子票唆,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 好奇觸摸事件是如何從屏幕轉(zhuǎn)移到APP內(nèi)的?困惑于Cell怎么突然不能點擊了辖所?糾結(jié)于如何實現(xiàn)這個奇葩響應(yīng)需求?亦或是...
    Lotheve閱讀 57,061評論 51 599
  • 在iOS開發(fā)中經(jīng)常會涉及到觸摸事件磨德。本想自己總結(jié)一下缘回,但是遇到了這篇文章,感覺總結(jié)的已經(jīng)很到位典挑,特此轉(zhuǎn)載酥宴。作者:L...
    WQ_UESTC閱讀 6,009評論 4 26
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,079評論 25 707
  • #iOS開發(fā)之UI篇#iOS開發(fā)之UI篇 #常用控件介紹1## #UI第09天:滾動視圖# ##UIScrollV...
    LennonLin閱讀 1,767評論 0 0
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點贊按鈕進度條TabLayout圖標下拉刷新...
    皇小弟閱讀 46,755評論 22 665