iOS實(shí)現(xiàn)UIImageView透明區(qū)域點(diǎn)擊事件穿透

問題

最近要在iPad上實(shí)現(xiàn)一個(gè)很獨(dú)特的功能,簡(jiǎn)單描述一下就是要顯示一個(gè)帶有半透明背景的彈出界面,在其上加一個(gè)不規(guī)則形狀的圖片,手指點(diǎn)擊這個(gè)彈出界面的半透明區(qū)域就退出這個(gè)彈出界面。

問題是UED/美工不會(huì)提供純粹的不規(guī)則形狀切圖,實(shí)際只能給出的是以不規(guī)則形狀加透明區(qū)域的矩形切圖,這就帶來另外一個(gè)要求:點(diǎn)擊矩形切圖的透明區(qū)域也要退出彈出界面。這就有點(diǎn)難辦了,透明區(qū)域也是不規(guī)則形狀的,該怎么判斷出手指點(diǎn)擊的點(diǎn)就是透明區(qū)域呢?

思路

一般在iOS的控件中,要不就是完全允許用戶點(diǎn)擊,要不就是禁止用戶交互,這是可以通過設(shè)置控件的userInteractionEnabled屬性來修改。如果添加的圖片不是不規(guī)則形狀的,而是矩形,這問題就簡(jiǎn)單多了,只需要將矩形圖片對(duì)應(yīng)的UIImageView的userInteractionEnabled設(shè)為YES,對(duì)半透明背景View(或者直接設(shè)置為一個(gè)按鈕)設(shè)置點(diǎn)擊事件處理,就可以點(diǎn)擊實(shí)現(xiàn)半透明背景退出彈出界面。

現(xiàn)在的情況是這個(gè)矩形圖片一分為二,一部分為實(shí)體的不規(guī)則形狀圖片,一部分為不規(guī)則形狀的透明區(qū)域。很顯然,問題的解決思路是:讓手指能“穿透”這個(gè)不規(guī)則透明區(qū)域去點(diǎn)擊背后的半透明背景,而不透明部分就不“穿透”。

前面說的userInteractionEnabled屬性只是簡(jiǎn)單地一刀切設(shè)置控件是否允許用戶操作(即可以響應(yīng)手指觸摸事件),更加靈活的設(shè)置方法是使用UIView的hitTest:withEvent:與pointInside:withEvent:。簡(jiǎn)單介紹下,iOS中的pointInside:withEvent:方法是用來判斷當(dāng)前的點(diǎn)擊或者觸摸事件的點(diǎn)是否在當(dāng)前的view中,它被hitTest:withEvent:調(diào)用,通過對(duì)每個(gè)子視圖調(diào)用pointInside:withEvent:決定最終哪個(gè)視圖來響應(yīng)此事件。如果一個(gè)子視圖的pointInside:withEvent:返回NO,說明這個(gè)子視圖不會(huì)響應(yīng)點(diǎn)擊事件,然后就去尋找更深層的子視圖來找到最終響應(yīng)觸摸事件;返回YES就說明子視圖能響應(yīng)點(diǎn)擊事件(但不一定是子視圖本身響應(yīng),若子視圖還有子視圖的話,還會(huì)繼續(xù)循環(huán)去找最終響應(yīng)事件的子子視圖)。

于是,本文的問題就可以這樣轉(zhuǎn)化:創(chuàng)建一個(gè)UIImageView的子類,重寫pointInside:withEvent:方法,讓矩形圖片的透明區(qū)域的pointInside:withEvent:返回NO,而非透明區(qū)域的pointInside:withEvent:返回YES,如果能達(dá)到這個(gè)要求,透明區(qū)域點(diǎn)擊事件穿透就能夠?qū)崿F(xiàn)。

現(xiàn)在的關(guān)鍵問題是怎么識(shí)別出這個(gè)透明區(qū)域。

iOS中通常用的圖片是PNG圖片,這種圖片有alpha通道,如果能獲取PNG圖片每個(gè)像素的alpha值,就不難判斷出手指點(diǎn)擊的圖片區(qū)域是不是透明的。

關(guān)鍵代碼如下:

Here’s Code

12345678910111213141516

-(BOOL)pointInside:(CGPoint)pointwithEvent:(UIEvent*)event{//Using code from http://stackoverflow.com/questions/1042830/retrieving-a-pixel-alpha-value-for-a-uiimageunsignedcharpixel[1]={0};CGContextRefcontext=CGBitmapContextCreate(pixel,1,1,8,1,NULL,kCGImageAlphaOnly);UIGraphicsPushContext(context);[self.imagedrawAtPoint:CGPointMake(-point.x,-point.y)];UIGraphicsPopContext();CGContextRelease(context);CGFloatalpha=pixel[0]/255.0f;BOOLtransparent=alpha<0.01f;return!transparent;}

解釋:

這段代碼是通過CGBitmapContextCreate方法創(chuàng)建只包含alpha通道的圖形上下文(真不知道context怎么翻譯為最好),這個(gè)圖形上下文的大小為1x1,也就是實(shí)際上只放得下一個(gè)像素,將矩形圖片手指觸摸點(diǎn)point繪制到這個(gè)圖形上下文中,那么pixel數(shù)組中唯一元素的值就是手指觸摸點(diǎn)那一個(gè)像素的alpha值,做歸一化為與0.01比較,如果小于0.01就表明手指觸摸點(diǎn)是透明的,這時(shí)候返回NO就能夠?qū)崿F(xiàn)穿透效果,相反大于0.01就不會(huì)穿透。

注意到代碼中用到的坐標(biāo)為(-point.x, -point.y),為什么會(huì)是負(fù)數(shù)呢?這是因?yàn)槿绻鹀ontext的區(qū)域大小與image一致的話,[image drawAtPoint:]就會(huì)將image全部繪制在context中,而實(shí)際上context只放得下一個(gè)像素,為了保證point點(diǎn)能剛好繪制在這個(gè)context上,就必須設(shè)置繪制的起始坐標(biāo)為(-point.x, -point.y)。

代碼中的UIGraphicsPushContext容易誤導(dǎo)人,看名字以為是將參數(shù)中指定的context push入棧,但是參數(shù)中的context明明就是剛創(chuàng)建的啊?其實(shí)它是將舊的context(默認(rèn)的context)入棧,再切換到新的context(也就是參數(shù)中指定的)繪制,執(zhí)行UIGraphicsPopContext后就會(huì)切換回舊的context,而在新的context上繪制的內(nèi)容完全不影響舊context(默認(rèn)context)。這與CGContextSaveGState和CGContextRestoreGState是有本質(zhì)區(qū)別的。

附CGBitmapContextCreate函數(shù)參數(shù)詳解:

原型:

123456789

CGContextRefCGBitmapContextCreate(void*data,size_twidth,size_theight,size_tbitsPerComponent,size_tbytesPerRow,CGColorSpaceRefcolorspace,CGBitmapInfobitmapInfo);

參數(shù):

data 指向要渲染的繪制內(nèi)存的地址。這個(gè)內(nèi)存塊的大小至少是(bytesPerRow*height)個(gè)字節(jié)

width bitmap的寬度,單位為像素

height bitmap的高度,單位為像素

bitsPerComponent 內(nèi)存中像素的每個(gè)組件的位數(shù).例如,對(duì)于32位像素格式和RGB 顏色空間,你應(yīng)該將這個(gè)值設(shè)為8.

bytesPerRow bitmap的每一行在內(nèi)存所占的比特?cái)?shù)

colorspace bitmap上下文使用的顏色空間。

bitmapInfo 指定bitmap是否包含alpha通道,像素中alpha通道的相對(duì)位置,像素組件是整形還是浮點(diǎn)型等信息的字符串。

描述:

當(dāng)你調(diào)用這個(gè)函數(shù)的時(shí)候,Quartz創(chuàng)建一個(gè)位圖繪制環(huán)境,也就是位圖上下文。當(dāng)你向上下文中繪制信息時(shí),Quartz把你要繪制的信息作為位圖數(shù)據(jù)繪制到指定的內(nèi)存塊。一個(gè)新的位圖上下文的像素格式由三個(gè)參數(shù)決定:每個(gè)組件的位數(shù),顏色空間,alpha選項(xiàng)。alpha值決定了繪制像素的透明性。這篇文章可以設(shè)置夜間模式,不懂可以問我。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 在iOS開發(fā)中經(jīng)常會(huì)涉及到觸摸事件。本想自己總結(jié)一下,但是遇到了這篇文章,感覺總結(jié)的已經(jīng)很到位,特此轉(zhuǎn)載。作者:L...
    WQ_UESTC閱讀 6,237評(píng)論 4 26
  • 18- UIBezierPath官方API中文翻譯(待校對(duì)) ----------------- 華麗的分割線 -...
    醉臥欄桿聽雨聲閱讀 1,159評(píng)論 1 1
  • 一篇搞定事件傳遞、響應(yīng)者鏈條、hitTest和pointInside的使用發(fā)生觸摸事件后,系統(tǒng)會(huì)將該事件加入到一個(gè)...
    克魯?shù)吕?/span>閱讀 1,193評(píng)論 0 1
  • 好奇觸摸事件是如何從屏幕轉(zhuǎn)移到APP內(nèi)的?困惑于Cell怎么突然不能點(diǎn)擊了?糾結(jié)于如何實(shí)現(xiàn)這個(gè)奇葩響應(yīng)需求?亦或是...
    Lotheve閱讀 59,436評(píng)論 51 604
  • Core Animation其實(shí)是一個(gè)令人誤解的命名。你可能認(rèn)為它只是用來做動(dòng)畫的,但實(shí)際上它是從一個(gè)叫做Laye...
    小貓仔閱讀 3,951評(píng)論 1 4

友情鏈接更多精彩內(nèi)容