問題
最近要在iPad上實現一個很獨特的功能,簡單描述一下就是要顯示一個帶有半透明背景的彈出界面,在其上加一個不規則形狀的圖片,手指點擊這個彈出界面的半透明區域就退出這個彈出界面。
問題是UED/美工不會提供純粹的不規則形狀切圖,實際只能給出的是以不規則形狀加透明區域的矩形切圖,這就帶來另外一個要求:點擊矩形切圖的透明區域也要退出彈出界面。這就有點難辦了,透明區域也是不規則形狀的,該怎麼判斷出手指點擊的點就是透明區域呢?
思路
一般在iOS的控件中,要不就是完全允許用戶點擊,要不就是禁止用戶交互,這是可以通過設置控件的userInteractionEnabled屬性來修改。如果添加的圖片不是不規則形狀的,而是矩形,這問題就簡單多了,只需要將矩形圖片對應的UIImageView的userInteractionEnabled設爲YES,對半透明背景View(或者直接設置爲一個按鈕)設置點擊事件處理,就可以點擊實現半透明背景退出彈出界面。
現在的情況是這個矩形圖片一分爲二,一部分爲實體的不規則形狀圖片,一部分爲不規則形狀的透明區域。很顯然,問題的解決思路是:讓手指能“穿透”這個不規則透明區域去點擊背後的半透明背景,而不透明部分就不“穿透”。
前面說的userInteractionEnabled屬性只是簡單地一刀切設置控件是否允許用戶操作(即可以響應手指觸摸事件),更加靈活的設置方法是使用UIView的hitTest:withEvent:與pointInside:withEvent:。簡單介紹下,iOS中的pointInside:withEvent:方法是用來判斷當前的點擊或者觸摸事件的點是否在當前的view中,它被hitTest:withEvent:調用,通過對每個子視圖調用pointInside:withEvent:決定最終哪個視圖來響應此事件。如果一個子視圖的pointInside:withEvent:返回NO,說明這個子視圖不會響應點擊事件,然後就去尋找更深層的子視圖來找到最終響應觸摸事件;返回YES就說明子視圖能響應點擊事件(但不一定是子視圖本身響應,若子視圖還有子視圖的話,還會繼續循環去找最終響應事件的子子視圖)。
於是,本文的問題就可以這樣轉化:創建一個UIImageView的子類,重寫pointInside:withEvent:方法,讓矩形圖片的透明區域的pointInside:withEvent:返回NO,而非透明區域的pointInside:withEvent:返回YES,如果能達到這個要求,透明區域點擊事件穿透就能夠實現。
現在的關鍵問題是怎麼識別出這個透明區域。
iOS中通常用的圖片是PNG圖片,這種圖片有alpha通道,如果能獲取PNG圖片每個像素的alpha值,就不難判斷出手指點擊的圖片區域是不是透明的。
關鍵代碼如下:
Here’s Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
解釋:
這段代碼是通過CGBitmapContextCreate方法創建只包含alpha通道的圖形上下文(真不知道context怎麼翻譯爲最好),這個圖形上下文的大小爲1x1,也就是實際上只放得下一個像素,將矩形圖片手指觸摸點point繪製到這個圖形上下文中,那麼pixel數組中唯一元素的值就是手指觸摸點那一個像素的alpha值,做歸一化爲與0.01比較,如果小於0.01就表明手指觸摸點是透明的,這時候返回NO就能夠實現穿透效果,相反大於0.01就不會穿透。
注意到代碼中用到的座標爲(-point.x, -point.y),爲什麼會是負數呢?這是因爲如果context的區域大小與image一致的話,[image drawAtPoint:]就會將image全部繪製在context中,而實際上context只放得下一個像素,爲了保證point點能剛好繪製在這個context上,就必須設置繪製的起始座標爲(-point.x, -point.y)。
代碼中的UIGraphicsPushContext容易誤導人,看名字以爲是將參數中指定的context push入棧,但是參數中的context明明就是剛創建的啊?其實它是將舊的context(默認的context)入棧,再切換到新的context(也就是參數中指定的)繪製,執行UIGraphicsPopContext後就會切換回舊的context,而在新的context上繪製的內容完全不影響舊context(默認context)。這與CGContextSaveGState和CGContextRestoreGState是有本質區別的。
附CGBitmapContextCreate函數參數詳解:
原型:
1 2 3 4 5 6 7 8 9 |
|
參數:
data 指向要渲染的繪製內存的地址。這個內存塊的大小至少是(bytesPerRow*height)個字節
width bitmap的寬度,單位爲像素
height bitmap的高度,單位爲像素
bitsPerComponent 內存中像素的每個組件的位數.例如,對於32位像素格式和RGB 顏色空間,你應該將這個值設爲8.
bytesPerRow bitmap的每一行在內存所佔的比特數
colorspace bitmap上下文使用的顏色空間。
bitmapInfo 指定bitmap是否包含alpha通道,像素中alpha通道的相對位置,像素組件是整形還是浮點型等信息的字符串。
描述:
當你調用這個函數的時候,Quartz創建一個位圖繪製環境,也就是位圖上下文。當你向上下文中繪製信息時,Quartz把你要繪製的信息作爲位圖數據繪製到指定的內存塊。一個新的位圖上下文的像素格式由三個參數決定:每個組件的位數,顏色空間,alpha選項。alpha值決定了繪製像素的透明性。