Core Animation基礎(chǔ)
Core Animation 利用了硬件加速和架構(gòu)上的優(yōu)化來實現(xiàn)快速渲染和實時動畫。當(dāng)視圖的drawRect:
方法首次被調(diào)用時,層會將描畫的結(jié)果捕捉到一個位圖中,并在隨后的重畫中盡可能使用這個緩存的位圖电爹,以避免調(diào)用開銷很大的drawRect:
方法。這個過程使 Core Animation 得以優(yōu)化合成操作,取得期望的性能当窗。
Core Animation 把和視圖對象相關(guān)聯(lián)的層存儲在一個被稱為層樹的層次結(jié)構(gòu)中。和視圖一 樣寸宵,層樹中的每個層都只有一個父層崖面,但可以嵌入任意數(shù)量的子層元咙。缺省情況下,層樹中對象的組織方式和視圖在視圖層次中的組織方式完全一樣巫员。但是庶香,您可以在層樹中添加層,而不同時添加相應(yīng)的視圖简识。當(dāng)您希望實現(xiàn)某種特殊的視覺效果赶掖、而又不需要在視圖上保持這種效果時,就可能需要這種技術(shù)七扰。
實際上奢赂,層對象是 iPhone OS 渲染和布局系統(tǒng)的推動力,大多數(shù)視圖屬性實際上是其層對象屬性的一個很薄的封裝颈走。當(dāng)您(直接使用CALayer對象)修改層樹上層對象的屬性時膳灶,您所做的改變會立即反映在層對象上。但是立由,如果該變化觸發(fā)了相應(yīng)的動畫轧钓,則可能不會立即反映在屏幕上,而是必須隨著時間的變化以動畫的形式表現(xiàn)在屏幕上锐膜。為了管理這種類型的動畫聋迎,Core Animation 額外維護(hù)兩組層對象,我們稱之為表示樹和渲染樹枣耀。
表示樹反映的是層在展示給用戶時的當(dāng)前狀態(tài)霉晕。假定您對層值的變化實行動畫,則在動畫開始時捞奕,表示層反映的是老的值牺堰;隨著動畫的進(jìn)行六水,Core Animation 會根據(jù)動畫的當(dāng)前幀來更新表示樹層的值请毛;然后,渲染樹就和表示樹一起部逮,將變化渲染在屏幕上院促。由于渲染樹運行在單獨的進(jìn)程或線程上筏养,所以它所做的工作并不影響應(yīng)用程序的主運行循環(huán)。雖然層樹和表示樹都是公開的常拓,但是渲染樹的接口是私有渐溶。
在視圖后面設(shè)置層對象對描畫代碼的性能有很多重要的影響。使用層的好處在于視圖的大多數(shù)幾何變化都不需要重畫弄抬。舉例來說茎辐,改變視圖的位置和尺寸并需要重畫視圖的內(nèi)容,只需簡單地重用層緩存的位圖就可以了。對緩存的內(nèi)容實行動畫比每次都重畫內(nèi)容要有效得多拖陆。
使用層的缺點在于層是額外的緩存數(shù)據(jù)弛槐,會增加應(yīng)用程序的內(nèi)存壓力。如果您的應(yīng)用程序創(chuàng)建太多的視圖依啰,或者創(chuàng)建多個很大的視圖乎串,則可能很快就會出現(xiàn)內(nèi)存不夠用的情形。您不用擔(dān)心在應(yīng)用程序中使用視圖速警,但是灌闺,如果有現(xiàn)成的視圖可以重用,就不要創(chuàng)建新的視圖對象坏瞄。換句話說桂对,您應(yīng)該設(shè)法使內(nèi)存中同時存在的視圖對象數(shù)量最小。
有關(guān) Core Animation 的進(jìn)一步概述鸠匀、對象樹蕉斜、以及如何創(chuàng)建動畫,請參見Core Animation編程指南缀棍。
改變視圖的層
在 iPhone OS 系統(tǒng)中宅此,由于視圖必須有一個與之關(guān)聯(lián)的層對象,所以 UIView 類在初始化時會自動創(chuàng)建相應(yīng)的層爬范。您可以通過視圖的 layer 屬性訪問這個層父腕,但是不能在視圖創(chuàng)建完成后改變層對象。
如果您希望視圖使用不同類型的層青瀑,必須重載其layerClass
類方法璧亮,并在該方法中返回您希望使用的層對象。使用不同層類的最常見理由是為了實現(xiàn)一個基于 OpenGL 的應(yīng)用程序斥难。為了使用 OpenGL 描畫命令枝嘶,視圖下面的層必須是 CAEAGLLayer 類的實例,這種類型的層可以和 OpenGL 渲染調(diào)用進(jìn)行交互哑诊,最終在屏幕上顯示期望的內(nèi)容群扶。
重要提示:您永遠(yuǎn)不應(yīng)修改視圖層的 delegate 屬性,該屬性用于存儲一個指向視圖的指針镀裤,應(yīng)該被認(rèn)為是私有的竞阐。類似地,由于一個視圖只能作為一個層的委托暑劝,所以您必須避免將它作為其它層對象的委托骆莹,否則會導(dǎo)致應(yīng)用程序崩潰。
動畫支持
iPhone OS 的每個視圖后面都有一個層對象铃岔,這樣做的好處之一是使視圖內(nèi)容更加易于實現(xiàn)動畫汪疮。請記住,動畫并不一定是為了在視覺上吸引眼球毁习,它可以將應(yīng)用程序界面變化的上下文呈現(xiàn)給用戶智嚷。舉例來說,當(dāng)您在屏幕轉(zhuǎn)移過程中使用過渡時纺且,過渡本身就向用戶指示屏幕之間的聯(lián)系盏道。系統(tǒng)自動支持了很多經(jīng)常使用的動畫,但您也可以為界面上的其它部分創(chuàng)建動畫载碌。
UIView 類的很多屬性都被設(shè)計為可動畫的(animatable)猜嘱。可動畫的屬性是指當(dāng)屬性從一個值變?yōu)榱硪粋€值的時候嫁艇,可以半自動地支持動畫朗伶。您仍然必須告訴 UIKit 希望執(zhí)行什么類型的動畫,但是動畫一旦開始步咪,Core Animation 就會全權(quán)負(fù)責(zé)论皆。UIView 對象中支持動畫的屬性有如下幾個:
- frame
- bounds
- center
- transform
- alpha
雖然其它的視圖屬性不直接支持動畫,但是您可以為其中的一部分顯式創(chuàng)建動畫猾漫。顯式動畫要求您做很多管理動畫和渲染內(nèi)容的工作点晴,通過使用 Core Animation 提供的基礎(chǔ)設(shè)施,這些工作仍然可以得到良好的性能悯周。
有關(guān)如何通過 UIView 類創(chuàng)建動畫的更多信息粒督,請參見“實現(xiàn)視圖動畫”部分;有關(guān)如何創(chuàng)建顯式動畫的更多信息禽翼,則請參見 Core Animation編程指南屠橄。
視圖坐標(biāo)系統(tǒng)
UIKit 中的坐標(biāo)是基于這樣的坐標(biāo)系統(tǒng):以左上角為坐標(biāo)的原點,原點向下和向右為坐標(biāo)軸正向闰挡。坐標(biāo)值由浮點數(shù)來表示仇矾,內(nèi)容的布局和定位因此具有更高的精度,還可以支持與分辨率無關(guān)的特性解总。圖1顯示了這個相對于屏幕的坐標(biāo)系統(tǒng)贮匕,這個坐標(biāo)系統(tǒng)同時也用于UIWindow和UIView類。視圖坐標(biāo)系統(tǒng)的方向和Quartz及Mac OS X使用的缺省方向不同花枫,選擇這個特殊的方向是為了使布局用戶界面上的控件及內(nèi)容更加容易刻盐。
圖1視圖坐標(biāo)系統(tǒng):
您在編寫界面代碼時,需要知道當(dāng)前起作用的坐標(biāo)系統(tǒng)劳翰。每個窗口和視圖對象都維護(hù)一個自己本地的坐標(biāo)系統(tǒng)敦锌。視圖中發(fā)生的所有描畫都是相對于視圖本地的坐標(biāo)系統(tǒng)。但是佳簸,每個視圖的邊框矩形都是通過其父視圖的坐標(biāo)系統(tǒng)來指定乙墙,而事件對象攜帶的坐標(biāo)信息則是相對于應(yīng)用程序窗口的坐標(biāo)系統(tǒng)颖变。為了方便,UIWindow 和 UIView 類都提供了一些方法听想,用于在不同對象之間進(jìn)行坐標(biāo)系統(tǒng)的轉(zhuǎn)換腥刹。
雖然 Quartz 使用的坐標(biāo)系統(tǒng)不以左上角為原點,但是對于很多 Quartz 調(diào)用來說汉买,這并不是問題衔峰。在調(diào)用視圖的drawRect:
方法之前,UIKit 會自動對描畫環(huán)境進(jìn)行配置蛙粘,使左上角成為坐標(biāo)系統(tǒng)的原點垫卤,在這個環(huán)境中發(fā)生的 Quartz 調(diào)用都可以正確地在視圖中描畫。您唯一需要考慮不同坐標(biāo)系統(tǒng)之間差別的場合是當(dāng)您自行通過 Quartz 建立描畫環(huán)境的時候出牧。
更多有關(guān)坐標(biāo)系統(tǒng)穴肘、Quartz、和描畫的一般信息舔痕,請參見“圖形和描畫”部分梢褐。
邊框、邊界赵讯、和中心的關(guān)系
視圖對象通過 frame盈咳、bounds、和center 屬性聲明來跟蹤自己的大小和位置边翼。frame 屬性包含一個矩形鱼响,即邊框矩形,用于指定視圖相對于其父視圖坐標(biāo)系統(tǒng)的位置和大小组底。bounds 屬性也包含一個矩形丈积,即邊界矩形,負(fù)責(zé)定義視圖相對于本地坐標(biāo)系統(tǒng)的位置和大小债鸡。雖然邊界矩形的原點通常被設(shè)置為 (0, 0)江滨,但這并不是必須的。center 屬性包含邊框矩形的中心點厌均。
在代碼中唬滑,您可以將 frame、bounds棺弊、和 center 屬性用于不同的目的晶密。邊界矩形代表視圖本地的坐標(biāo)系統(tǒng),因此模她,在描畫和事件處理代碼中稻艰,經(jīng)常借助它來取得視圖中發(fā)生事件或需要更新的位置。中心點代表視圖的中心侈净,改變中心點一直是移動視圖位置的最好方法尊勿。邊框矩形是一個通過 bounds 和 center 屬性計算得到的便利值僧凤,只有當(dāng)視圖的變換屬性被設(shè)置恒等變換時,邊框矩形才是有效的元扔。
圖2顯示了邊框矩形和邊界矩形之間的關(guān)系躯保。右邊的整個圖像是從視圖的(0, 0)開始描畫的,但是由于邊界的大小和整個圖像的尺寸不相匹配摇展,所以位于邊界矩形之外的圖像部分被自動裁剪吻氧。在視圖和它的父視圖進(jìn)行合成的時候溺忧,視圖在其 父視圖中的位置是由視圖邊框矩形的原點決定的咏连。在這個例子中,該原點是(5, 5)鲁森。結(jié)果祟滴,視圖的內(nèi)容就相對于父視圖的原點向下向右移動相應(yīng)的尺寸。
視圖的邊框和邊界之間的關(guān)系:
如果沒有經(jīng)過變換歌溉,視圖的位置和大小就由上述三個互相關(guān)聯(lián)的屬性決定的垄懂。當(dāng)您在代碼中通過initWithFrame:
方法創(chuàng)建一個視圖對象時,其 frame 屬性就會被設(shè)置痛垛。該方法同時也將 bounds 矩形的原點初始化為(0.0, 0.0)草慧,大小則和視圖的邊框相同。然后 center 屬性會被設(shè)置為邊框的中心點匙头。
雖然您可以分別設(shè)置這些屬性的值漫谷,但是設(shè)置其中的一個屬性會引起其它屬性的改變,具體關(guān)系如下:
- 當(dāng)您設(shè)置frame屬性時蹂析,bounds屬性的大小會被設(shè)置為與frame屬性的大小相匹配的值舔示,center屬性也會被調(diào)整為與新的邊框中心點相匹配的值。
- 當(dāng)您設(shè)置center屬性時电抚,frame的原點也會隨之改變惕稻。
- 當(dāng)您設(shè)置bounds矩形的大小時,frame矩形的大小也會隨之改變蝙叛。
您可以改變bounds的原點而不影響其它兩個屬性俺祠。當(dāng)您這樣做時,視圖會顯示您標(biāo)識的圖形部分借帘。在圖2中锻煌,邊界的原點被設(shè)置為(0.0, 0.0)。在圖3中姻蚓,該原點被移動到(8.0, 24.0)宋梧。結(jié)果,顯示出來的是視圖圖像的不同部分狰挡。但是捂龄,由于邊框矩形并沒有改變释涛,新的內(nèi)容在父視圖中的位置和之前是一樣的。
改變視圖的邊界:
請注意:缺省情況下倦沧,視圖的邊框并不會被父視圖的邊框裁剪唇撬。如果您希望讓一個視圖裁剪其子視圖,需要將其 clipsToBounds 屬性設(shè)置為YES展融。
坐標(biāo)系統(tǒng)變換
在視圖的drawRect:
方法中常常借助坐標(biāo)系統(tǒng)變換來進(jìn)行描畫窖认。而在iPhone OS系統(tǒng)中,您還可以用它來實現(xiàn)視圖的某些視覺效果告希。舉例來說扑浸,UIView類中包含一個transform屬性聲明,您可以通過它來對整個視圖實行各種類型的平移燕偶、比例縮放喝噪、和變焦縮放效果。缺省情況下指么,這個屬性的值是一個恒等變換酝惧,不會改變視圖的外觀。在加入變換之前伯诬,首先要得到該屬性中存儲的CGAffineTransform結(jié)構(gòu)晚唇,用相應(yīng)的Core Graphics函數(shù)實行變換,然后再將修改后的變換結(jié)構(gòu)重新賦值給視圖的transform屬性盗似。
請注意:當(dāng)您將變換應(yīng)用到視圖時哩陕,所有執(zhí)行的變換都是相對于視圖的中心點。
平移一個視圖會使其所有的子視圖和視圖本身的內(nèi)容一起移動桥言。由于子視圖的坐標(biāo)系統(tǒng)是繼承并建立在這些變化的基礎(chǔ)上的萌踱,所以比例縮放也會影響子視圖的描畫。有關(guān)如何控制視圖內(nèi)容縮放的更多信息号阿,請參見“內(nèi)容模式和比例縮放”部分并鸵。
重要提示:如果transform屬性的值不是恒等變換,則B屬性的值就是未定義的扔涧,必須被忽略园担。在設(shè)置變換屬性之后,請使用bounds和center屬性來獲取視圖的位置和大小枯夜。
有關(guān)如何在drawRect:
方法中使用變換的信息弯汰,請參見“坐標(biāo)和坐標(biāo)變換”部分;有關(guān)用于修改CGAffineTransform結(jié)構(gòu)的函數(shù)湖雹,則請參見CGAffineTransform參考咏闪。
內(nèi)容模式與比例縮放
當(dāng)您改變視圖的邊界,或者將一個比例因子應(yīng)用到視圖的transform屬性聲明時摔吏,邊框矩形會發(fā)生等量的變化鸽嫂。根據(jù)內(nèi)容模式的不同纵装,視圖的內(nèi)容也可能被縮放或重新定位,以反映上述的變化据某。視圖的contentMode屬性決定了邊界變化和縮放操作作用到視圖上產(chǎn)生的效果橡娄。缺省情況下,這個屬性的值被設(shè)置為UIViewContentModeScaleToFill癣籽,意味著視圖內(nèi)容總是被縮放挽唉,以適應(yīng)新的邊框尺寸。作為例子筷狼,圖4顯示了當(dāng)視圖的水平縮放因子放大一倍時產(chǎn)生的效果瓶籽。
使用scale-to-fill內(nèi)容模式縮放視圖:
視圖內(nèi)容的縮放僅在首次顯示視圖的時候發(fā)生,渲染后的內(nèi)容會被緩存在視圖下面的層上桑逝。當(dāng)邊界或縮放因子發(fā)生變化時棘劣,UIKit并不強(qiáng)制視圖進(jìn)行重畫俏让,而是根據(jù)其內(nèi)容模式?jīng)Q定如何顯示緩存的內(nèi)容楞遏。圖5比較了在不同的內(nèi)容模式下,改變視圖邊界或應(yīng)用不同的比例縮放因子時產(chǎn)生的結(jié)果首昔。
內(nèi)容模式比較:
對視圖應(yīng)用一個比例縮放因子總是會使其內(nèi)容發(fā)生縮放寡喝,而邊界的改變在某些內(nèi)容模式下則不會發(fā)生同樣的結(jié)果。不同的UIViewContentMode常量(比如UIViewContentModeTop和UIViewContentModeBottomRight)可以使當(dāng)前的內(nèi)容在視圖的不同角落或沿著視圖的不同邊界顯示勒奇,還有一種模式可以將內(nèi)容顯示在視圖的中心预鬓。在這些模式的作用下,改變邊界矩形只會簡單地將現(xiàn)有的視圖內(nèi)容移動到新的邊界矩形中對應(yīng)的位置上赊颠。
當(dāng)您希望在應(yīng)用程序中實現(xiàn)尺寸可調(diào)整的控件時格二,請務(wù)必考慮使用內(nèi)容模式。這樣做可以避免控件的外觀發(fā)生變形竣蹦,以及避免編寫定制的描畫代 碼顶猜。按鍵和分段控件(segmented control)特別適合基于內(nèi)容模式的描畫。它們通常使用幾個圖像來創(chuàng)建控件外觀痘括。除了有兩個固定尺寸的蓋帽圖像之外长窄,按鍵可以通過一個可伸展的、寬度 只有一個像素的中心圖像來實現(xiàn)水平方向的尺寸調(diào)整纲菌。它將每個圖像顯示在自己的圖像視圖中挠日,而將可伸展的中間圖像的內(nèi)容模式設(shè)置為UIViewContentModeScaleToFill,使得在尺寸調(diào)整時兩端的外觀不會變形翰舌。更為重要的是嚣潜,每個圖像視圖的關(guān)聯(lián)圖像都可以由Core Animation來緩存,因此不需要編寫描畫代碼就可以支持動畫椅贱,從而使大大提高了性能懂算。
內(nèi)容模式通常有助于避免視圖內(nèi)容的描畫唉韭,但是當(dāng)您希望對縮放和尺寸調(diào)整過程中的視圖外觀進(jìn)行特別的控制時,也可以使用UIViewContentModeRedraw模式犯犁。將視圖的內(nèi)容模式設(shè)置為這個值可以強(qiáng)制Core Animation使視圖的內(nèi)容失效属愤,并調(diào)用視圖的drawRect:
方法,而不是自動進(jìn)行縮放或尺寸調(diào)整酸役。
自動尺寸調(diào)整行為
當(dāng)您改變視圖的邊框矩形時住诸,其內(nèi)嵌子視圖的位置和尺寸往往也需要改變,以適應(yīng)原始視圖的新尺寸涣澡。如果視圖的autoresizesSubviews屬性聲明被設(shè)置為YES贱呐,則其子視圖會根據(jù)autoresizingMask屬性的值自動進(jìn)行尺寸調(diào)整。簡單配置一下視圖的自動尺寸調(diào)整掩碼常常就能使應(yīng)用程序得到合適的行為入桂;否則奄薇,應(yīng)用程序就必須通過重載layoutSubviews方法來提供自己的實現(xiàn)。
設(shè)置視圖的自動尺寸調(diào)整行為的方法是通過位OR操作符將期望的自動尺寸調(diào)整常量連結(jié)起來抗愁,并將結(jié)果賦值給視圖的autoresizingMask屬性馁蒂。表列舉了自動尺寸調(diào)整常量,并描述這些常量如何影響給定視圖的尺寸和位置蜘腌。舉例來說沫屡,如果要使一個視圖和其父視圖左下角的相對位置保持不變,可以加入UIViewAutoresizingFlexibleRightMargin和UIViewAutoresizingFlexibleTopMargin常量撮珠,并將結(jié)果賦值給autoresizingMask屬性沮脖。當(dāng)同一個軸向有多個部分被設(shè)置為可變時,尺寸調(diào)整的裕量會被平均分配到各個部分上芯急。
表2-1 自動尺寸調(diào)整掩碼常量
自動尺寸調(diào)整掩碼 | 描述 |
---|---|
UIViewAutoresizingNone | 這個常量如果被設(shè)置勺届,視圖將不進(jìn)行自動尺寸調(diào)整。 |
UIViewAutoresizingFlexibleHeight | 這個常量如果被設(shè)置娶耍,視圖的高度將和父視圖的高度一起成比例變化免姿。否則,視圖的高度將保持不變伺绽。 |
UIViewAutoresizingFlexibleWidth | 這個常量如果被設(shè)置养泡,視圖的寬度將和父視圖的寬度一起成比例變化。否則奈应,視圖的寬度將保持不變澜掩。 |
UIViewAutoresizingFlexibleLeftMargin | 這個常量如果被設(shè)置,視圖的左邊界將隨著父視圖寬度的變化而按比例進(jìn)行調(diào)整杖挣。否則肩榕,視圖和其父視圖的左邊界的相對位置將保持不變。 |
UIViewAutoresizingFlexibleRightMargin | 這個常量如果被設(shè)置,視圖的右邊界將隨著父視圖寬度的變化而按比例進(jìn)行調(diào)整株汉。否則筐乳,視圖和其父視圖的右邊界的相對位置將保持不變。 |
UIViewAutoresizingFlexibleBottomMargin | 這個常量如果被設(shè)置乔妈,視圖的底邊界將隨著父視圖高度的變化而按比例進(jìn)行調(diào)整蝙云。否則,視圖和其父視圖的底邊界的相對位置將保持不變路召。 |
UIViewAutoresizingFlexibleTopMargin | 這個常量如果被設(shè)置勃刨,視圖的上邊界將隨著父視圖高度的變化而按比例進(jìn)行調(diào)整。否則股淡,視圖和其父視圖的上邊界的相對位置將保持不變身隐。 |
圖6為這些常量值的位置提供了一個圖形表示。如果這些常量之一被省略唯灵,則視圖在相應(yīng)方向上的布局就被固定贾铝;如果某個常量被包含在掩碼中,在該方向的視圖布局就就靈活的埠帕。
視圖的自動尺寸調(diào)整掩碼常量
如果您通過Interface Builder配置視圖垢揩,則可以用Size查看器的Autosizing控制來設(shè)置每個視圖的自動尺寸調(diào)整行為。上圖中的靈活寬度及高度常量和 Interface Builder中位于同樣位置的彈簧具有同樣的行為搞监,但是空白常量的行為則是正好相反水孩。換句話說镰矿,如果要將靈活右空白的自動尺寸調(diào)整行為應(yīng)用到 Interface Builder的某個視圖琐驴,必須使相應(yīng)方向空間的Autosizing控制為空,而不是放置一個支柱秤标。幸運的是绝淡,Interface Builder通過動畫顯示了您的修改對視圖自動尺寸調(diào)整行為的影響。
如果視圖的autoresizesSubviews屬性被設(shè)置為NO苍姜,則該視圖的直接子視圖的所有自動尺寸調(diào)整行為將被忽略牢酵。類似地沮稚,如果一個子視圖的自動尺寸調(diào)整掩碼被設(shè)置為UIViewAutoresizingNone矢赁,則該子視圖的尺寸將不會被調(diào)整恰梢,因而其直接子視圖的尺寸也不會被調(diào)整举塔。
請注意:為了使自動尺寸調(diào)整的行為正確定拟,視圖的transform屬性必須設(shè)置為恒等變換瞳腌;其它變換下的尺寸自動調(diào)整行為是未定義的唠梨。
自動尺寸調(diào)整行為可以適合一些布局的要求碴萧,但是如果您希望更多地控制視圖的布局棵譬,可以在適當(dāng)?shù)囊晥D類中重載layoutSubviews方法显蝌。有關(guān)視圖布局管理的更多信息,請參見“響應(yīng)布局的變化”部分订咸。
創(chuàng)建和管理視圖層次
管理用戶界面的視圖層次是開發(fā)應(yīng)用程序用戶界面的關(guān)鍵部分曼尊。視圖的組織方式不僅定義了應(yīng)用程序的視覺外觀酬诀,而且還定義了應(yīng)用程序如何響 應(yīng)變化。視圖層次中的父-子關(guān)系可以幫助我們定義應(yīng)用程序中負(fù)責(zé)處理觸摸事件的對象鏈骆撇。當(dāng)用戶旋轉(zhuǎn)設(shè)備時瞒御,父-子關(guān)系也有助于定義每個視圖的尺寸和位置是 如何隨著界面方向的變化而變化的。
圖7顯示了一個簡單的例子神郊,說明如何通過視圖的分層來創(chuàng)建期望的視覺效果葵腹。在Clock程序中,頁簽條和導(dǎo)航條視圖屿岂,以及定制視圖混合在一起践宴,實現(xiàn)了整個界面。
Clock程序的視圖層
如果您探究Clock程序中視圖之間的關(guān)系爷怀,就會發(fā)現(xiàn)它們很像“改變視圖的層”部分中顯示的關(guān)系阻肩,窗口對象是應(yīng)用程序的頁簽條、導(dǎo)航條运授、和定制視圖的根視圖烤惊。
Clock程序的視圖層次
在iPhone應(yīng)用程序的開發(fā)過程中,有幾種建立視圖層次的方法吁朦,包括基于Interface Builder的可視化方法和通過代碼編程的方法柒室。本文的下面部分將向您介紹如何裝配視圖層次,以及如何在建立視圖層次之后尋找其中的視圖逗宜,還有如何在不 同的視圖坐標(biāo)系統(tǒng)之間進(jìn)行轉(zhuǎn)換雄右。
創(chuàng)建一個視圖對象
創(chuàng)建視圖對象的最簡單方法是使用Interface Builder進(jìn)行制作,然后將視圖對象從作成的nib文件載 入內(nèi)存纺讲。在Interface Builder的圖形環(huán)境中擂仍,您可以將新的視圖從庫中拖出,然后放到窗口或另一個視圖中熬甚,以快速建立需要的視圖層次逢渔。Interface Builder使用的是活的視圖對象,因此乡括,當(dāng)您用這個圖形環(huán)境構(gòu)建用戶界面時肃廓,所看到的就是運行時裝載的外觀,而且不需要為視圖層次中的每個視圖編寫單 調(diào)乏味的內(nèi)存分配和初始化代碼诲泌。
如果您不喜歡Interface Builder和nib文件盲赊,也可以通過代碼來創(chuàng)建視圖。創(chuàng)建一個新的視圖對象時档礁,需要為其分配內(nèi)存角钩,并向該對象發(fā)送一個initWithFrame:
消息,以對其進(jìn)行初始化。舉例來說递礼,如果您要創(chuàng)建一個新的UIView類的實例作為其它視圖的容器惨险,則可以使用下面的代碼:
CGRect viewRect = CGRectMake(0, 0, 100, 100);
UIView* myView = [[UIView alloc] initWithFrame:viewRect];
請注意:雖然所有系統(tǒng)提供的視圖對象都支持initWithFrame:
消息,但是其中的一部分可能有自己偏好的初始化方法脊髓,您應(yīng)該使用那些方法辫愉。有關(guān)定制初始化方法的更多信息,請參見相應(yīng)的類參考文檔将硝。
您在視圖初始化時指定的邊框矩形代表該視圖相對于未來父視圖的位置和大小恭朗。在將視圖顯示于屏幕上之前,您需要將它加入到窗口或其它視圖中依疼。在這個時候痰腮,UIKit 會根據(jù)您指定的邊框矩形將視圖放置到其父視圖的相應(yīng)位置中。有關(guān)如何將視圖添加到視圖層次的信息律罢,請參見“添加和移除子視圖”部分膀值。
添加和移除子視圖
Interface Builder 是建立視圖層次的最便利工具,因為它可以讓您看到視圖在運行時的外觀误辑。在界面制作完成后沧踏,它將視圖對象及其層次關(guān)系保存在nib文件中。在 運行時巾钉,系統(tǒng)會按照nib文件的內(nèi)容為應(yīng)用程序重新創(chuàng)建那些對象和關(guān)系翘狱。當(dāng)一個nib文件被裝載時,系統(tǒng)會自動調(diào)用重建視圖層次所需要的UIView方法砰苍。
如果您不喜歡通過Interface Builder和nib文件來創(chuàng)建視圖層次潦匈,則可以通過代碼來創(chuàng)建。如果一個視圖必須具有某些子視圖才能工作师骗,則應(yīng)該在其initWithFrame:
方法中進(jìn)行對其創(chuàng)建历等,以確保子視圖可以和視圖一起被顯示和初始化。如果子視圖是應(yīng)用程序設(shè)計的一部分(而不是視圖工作必需的)辟癌,則應(yīng)該在視圖的初始化代碼之外進(jìn)行創(chuàng)建。在iPhone程序中荐捻,有兩個地方最常用于創(chuàng)建視圖和子視圖黍少,它們是應(yīng)用程序委托對象的applicationDidFinishLaunching:
方法和視圖控制器的loadView
方法。
您可以通過下面的方法來操作視圖層次中的視圖對象:
- 調(diào)用父視圖的
addSubview:
方法來添加視圖处面,該方法將一個視圖添加到子視圖列表的最后厂置。 - 調(diào)用父視圖的
insertSubview:...
方法可以在父視圖的子視圖列表中間插入視圖。 - 調(diào)用父視圖的
bringSubviewToFront:
魂角、sendSubviewToBack:
昵济、或exchangeSubviewAtIndex:withSubviewAtIndex:
方法可以對父視圖的子視圖進(jìn)行重新排序。使用這些方法比從父視圖中移除子視圖并再次插入要快一些。 - 調(diào)用子視圖(而不是父視圖)的
removeFromSuperview
方法可以將子視圖從父視圖中移除访忿。
在添加子視圖時瞧栗,UIKit會根據(jù)子視圖的當(dāng)前邊框矩形確定其在父視圖中的初始位置。您可以隨時通過修改子視圖的frame屬性聲明來改變其位置海铆。缺省情況下迹恐,邊框位于父視圖可視邊界外部的子視圖不會被裁剪。如果您希望激活裁剪功能卧斟,必須將父視圖的clipsToBounds屬性設(shè)置為YES殴边。
程序清單1顯示了一個應(yīng)用程序委托對象的applicationDidFinishLaunching:
方法示例。在這個例子中珍语,應(yīng)用程序委托在啟動時通過代碼創(chuàng)建全部的用戶界面锤岸。界面中包含兩個普通的UIView對象,用于顯示基本顏色板乙。每個視圖都被嵌入到窗口中能耻,窗口也是 UIView 的一個子類,因此可以作為父視圖亡驰。父視圖會保持它們的子視圖晓猛,因此這個方法釋放了新創(chuàng)建的視圖對象,以避免重復(fù)保持凡辱。
程序清單1 創(chuàng)建一個帶有視圖的窗口
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Create the window object and assign it to the
// window instance variable of the application delegate.
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
window.backgroundColor = [UIColor whiteColor];
// Create a simple red square
CGRect redFrame = CGRectMake(10, 10, 100, 100);
UIView *redView = [[UIView alloc] initWithFrame:redFrame];
redView.backgroundColor = [UIColor redColor];
// Create a simple blue square
CGRect blueFrame = CGRectMake(10, 150, 100, 100);
UIView *blueView = [[UIView alloc] initWithFrame:blueFrame];
blueView.backgroundColor = [UIColor blueColor];
// Add the square views to the window
[window addSubview:redView];
[window addSubview:blueView];
// Once added to the window, release the views to avoid the
// extra retain count on each of them.
[redView release];
[blueView release];
// Show the window.
[window makeKeyAndVisible];
}
重要提示:在內(nèi)存管理方面戒职,可以將子視圖考慮為其它的集合對象。特別是當(dāng)您通過addSubview:
方法將一個視圖作為子視圖插入時透乾,父視圖會對其進(jìn)行保持操作洪燥。反過來,當(dāng)您通過removeFromSuperview
方法將子視圖從父視圖移走時乳乌,子視圖會被自動釋放捧韵。在將視圖加入視圖層次之后釋放該對象可以避免多余的保持操作,從而避免內(nèi)存泄露汉操。
有關(guān)Cocoa內(nèi)存管理約定的更多信息再来,請參見Cocoa內(nèi)存管理編程指南芒篷。
當(dāng)您為某個視圖添加子視圖時采缚,UIKit會向相應(yīng)的父子視圖發(fā)送幾個消息,通知它們當(dāng)前發(fā)生的狀態(tài)變化篡帕。您可以在自己的定制視圖中對諸如willMoveToSuperview:
殖侵、willMoveToWindow:
、willRemoveSubview:
镰烧、didAddSubview:
拢军、didMoveToSuperview
、和didMoveToWindow
這樣的方法進(jìn)行重載拌滋,以便在事件發(fā)生的前后進(jìn)行必要的處理朴沿,并根據(jù)發(fā)生的變化更新視圖的狀態(tài)信息。
在視圖層次建立之后败砂,您可以通過視圖的superview屬性來取得其父視圖赌渣,或者通過subviews屬性取得視圖的子視圖。您也可以通過isDescendantOfView:
方法來判定一個視圖是否在其父視圖的視圖層中昌犹。一個視圖層次的根視圖沒有父視圖坚芜,因此其superview屬性被設(shè)置為nil。對于當(dāng)前被顯示在屏幕上的視圖斜姥,窗口對象通常是整個視圖層次的根視圖鸿竖。
您可以通過視圖的window屬性來取得指向其父窗口(如果有的話)的指針,如果視圖還沒有被鏈接到窗口上铸敏,則該屬性會被設(shè)置為nil缚忧。
視圖層次中的坐標(biāo)轉(zhuǎn)換
很多時候,特別是處理事件的時候杈笔,應(yīng)用程序可能需要將一個相對于某邊框的坐標(biāo)值轉(zhuǎn)換為相對于另一個邊框的值闪水。例如球榆,觸摸事件通常使用基于窗口指標(biāo)系統(tǒng)的坐標(biāo)值來報告事件發(fā)生的位置持钉,但是視圖對象需要的是相對于視圖本地坐標(biāo)的位置信息每强,兩者可能是不一樣的。UIView類定義了下面這些方法,用于在不同的視圖本地坐標(biāo)系統(tǒng)之間進(jìn)行坐標(biāo)轉(zhuǎn)換:
convertPoint:fromView:
convertRect:fromView:
convertPoint:toView:
convertRect:toView:
convert...:fromView:
方法將指定視圖的坐標(biāo)值轉(zhuǎn)換為視圖本地坐標(biāo)系統(tǒng)的坐標(biāo)值;
convert...:toView:
方法則將視圖本地坐標(biāo)系統(tǒng)的坐標(biāo)值轉(zhuǎn)換為指定視圖坐標(biāo)系統(tǒng)的坐標(biāo)值房待。如果傳入nil作為視圖引用參數(shù)的值拜鹤,則上面這些方法會將視圖所在窗口的坐標(biāo)系統(tǒng)作為轉(zhuǎn)換的源或目標(biāo)坐標(biāo)系統(tǒng)敏簿。
除了UIView的轉(zhuǎn)換方法之外,UIWindow類也定義了幾個轉(zhuǎn)換方法蜻势。這些方法和UIView的版本類似,只是UIView定義的方法將視圖本地坐標(biāo)系統(tǒng)作為轉(zhuǎn)換的源或目標(biāo)坐標(biāo)系統(tǒng)挠铲,而UIWindow的版本則使用窗口坐標(biāo)系統(tǒng)拂苹。
convertPoint:fromWindow:
convertRect:fromWindow:
convertPoint:toWindow:
convertRect:toWindow:
當(dāng)參與轉(zhuǎn)換的視圖沒有被旋轉(zhuǎn),或者被轉(zhuǎn)換的對象僅僅是點的時候音羞,坐標(biāo)轉(zhuǎn)換相當(dāng)直接。如果是在旋轉(zhuǎn)之后的視圖之間轉(zhuǎn)換矩形或尺寸數(shù)據(jù)窘面,則其幾何結(jié)構(gòu)必須經(jīng)過合理的改變,才能得到正確的結(jié)果坐標(biāo)酣难。在對矩形結(jié)構(gòu)進(jìn)行轉(zhuǎn)換時憨募,UIView類假定您希望保證原來的屏幕區(qū)域被覆蓋珠漂,因此轉(zhuǎn)換后的矩形會被放大媳危,其結(jié)果是使放大后的矩形(如果放在對應(yīng)的視圖中)可以完全覆蓋原來的矩形區(qū)域。圖9顯示了將rotatedView對象的坐標(biāo)系統(tǒng)中的矩形轉(zhuǎn)換到其超類(outerView)坐標(biāo)系統(tǒng)的結(jié)果滋觉。
對旋轉(zhuǎn)后視圖中的值進(jìn)行轉(zhuǎn)換
對于尺寸信息,UIView簡單地將它處理為分別相對于源視圖和目標(biāo)視圖(0.0, 0.0)點的偏移量我纪。雖然偏移量保持不變,但是相對于坐標(biāo)軸的差額會隨著視圖的旋轉(zhuǎn)而移動术健。在轉(zhuǎn)換尺寸數(shù)據(jù)時,UIKit總是返回正的數(shù)值勘伺。
標(biāo)識視圖
UIView類中包含一個tag屬性飞醉。借助這個屬性噪裕,您可以通過一個整數(shù)值來標(biāo)識一個視圖對象。您可以通過這個屬性來唯一標(biāo)識視圖層次中的視圖铃诬,以及在運行時進(jìn)行視圖的檢索(基于tag標(biāo)識的檢索比您自行遍歷視圖層次要快)趣席。tag屬性的缺省值為0。
您可以通過UIView的viewWithTag:
方法來檢索標(biāo)識過的視圖霉涨。該方法從消息的接收者自身開始,通過深度優(yōu)先的方法來檢索接收者的子視圖往枷。
在運行時修改視圖
應(yīng)用程序在接收用戶輸入時错洁,需要通過調(diào)整自己的用戶界面來進(jìn)行響應(yīng)。應(yīng)用程序可能重新排列界面上的視圖窿锉、刷新屏幕上模型數(shù)據(jù)已被改變的 視圖、或者裝載一組全新的視圖洼滚。在決定使用哪種技術(shù)時遥巴,要考慮您的用戶界面拾弃,以及您希望實現(xiàn)什么豪椿。但是,如何初始化這些技術(shù)對于所有應(yīng)用程序都是一樣的。 本章的下面部分將描述這些技術(shù)蝌以,以及如何通過這些技術(shù)在運行時更新您的用戶界面饼灿。
請注意:如果您需要了解UIKit如何在框架內(nèi)部和您的定制代碼之間轉(zhuǎn)移事件和消息的背景信息,請在繼續(xù)閱讀本文之前查閱“視圖交互模型”部分庇忌。
實現(xiàn)視圖動畫
動畫為用戶界面在不同狀態(tài)之間的遷移過程提供流暢的視覺效果。在iPhone OS中占拍,動畫被廣泛用于視圖的位置調(diào)整表牢、尺寸變化、甚至是alpha值的變化(以實現(xiàn)淡入淡出的效果)位谋。動畫支持對于制作易于使用的應(yīng)用程序是至關(guān)重要的掏父,因此,UIKit直接將它集成到UIView類中,以簡化動畫的創(chuàng)建過程。
UIView類定義了幾個內(nèi)在支持動畫的屬性聲明—也就是說处渣,當(dāng)這些屬性值發(fā)生變化時,視圖為其變化過程提供內(nèi)建的動畫支持荠诬。雖然執(zhí)行動畫所需要的工作由UIView類自動完成,但您仍然必須在希望執(zhí)行動畫時通知視圖钧嘶。為此,您需要將改變給定屬性的代碼包裝在一個動畫塊中书幕。
動畫塊從調(diào)用UIView的beginAnimations:context:
類方法開始,而以調(diào)用commitAnimations
類方法作為結(jié)束智袭。在這兩個調(diào)用之間,您可以配置動畫的參數(shù)和改變希望實行動畫的屬性值瞳步。一旦調(diào)用commitAnimations
方法劣坊,UIKit就會開始執(zhí)行動畫,即把給定屬性從當(dāng)前值到新值的變化過程用動畫表現(xiàn)出來局冰。動畫塊可以被嵌套测蘑,但是在最外層的動畫塊提交之前,被嵌套的動畫不會被執(zhí)行康二。
表2-2列舉了UIView類中支持動畫的屬性碳胳。
表2-2 支持動畫的屬性
屬性 | 描述 |
---|---|
frame | 視圖的邊框矩形,位于父視圖的坐標(biāo)系中沫勿。 |
bounds | 視圖的邊界矩形藕帜,位于視圖的坐標(biāo)系中隘弊。 |
center | 邊框的中心,位于父視圖的坐標(biāo)系中。 |
transform | 視圖上的轉(zhuǎn)換矩陣九昧,相對于視圖邊界的中心规个。 |
alpha | 視圖的alpha值涣狗,用于確定視圖的透明度探遵。 |
配置動畫的參數(shù)
除了在動畫塊中改變屬性值之外拷况,您還可以對其它參數(shù)進(jìn)行配置杜恰,以確定您希望得到的動畫行為。為此知染,您可以調(diào)用下面這些UIView的類方法:
- 用
setAnimationStartDate:
方法來設(shè)置動畫在commitAnimations
方法返回之后的發(fā)生日期涧狮。缺省行為是使動畫立即在動畫線程中執(zhí)行拜银。 - 用
setAnimationDelay:
方法來設(shè)置實際發(fā)生動畫和commitAnimations
方法返回的時間點之間的間隔。 - 用
setAnimationDuration:
方法來設(shè)置動畫持續(xù)的秒數(shù)本缠。 - 用
setAnimationCurve:
方法來設(shè)置動畫過程的相對速度,比如動畫可能在啟示階段逐漸加速吞瞪,而在結(jié)束階段逐漸減速,或者整個過程都保持相同的速度。 - 用
setAnimationRepeatCount:
方法來設(shè)置動畫的重復(fù)次數(shù)条舔。 - 用
setAnimationRepeatAutoreverses:
方法來指定動畫在到達(dá)目標(biāo)值時是否自動反向播放说墨。您* * 可以結(jié)合使用這個方法和setAnimationRepeatCount:
方法,使各個屬性在初始值和目標(biāo)值之間平滑切換一段時間柜裸。
commitAnimations類方法在調(diào)用之后和動畫開始之前立刻返回。UIKit在一個獨立的、和應(yīng)用程序的主事件循環(huán)分離的線程中執(zhí)行動畫。commitAnimations方法將動畫發(fā)送到該線程,然后動畫就進(jìn)入線程中的隊列,直到被執(zhí)行。缺省情況下凶朗,只有在當(dāng)前正在運行的動畫塊執(zhí)行完成后瓷胧,Core Animation才會啟動隊列中的動畫。但是棚愤,您可以通過向動畫塊中的setAnimationBeginsFromCurrentState:
類方法傳入YES來重載這個行為搓萧,使動畫立即啟動。這樣做會停止當(dāng)前正在執(zhí)行的動畫宛畦,而使新動畫在當(dāng)前狀態(tài)下開始執(zhí)行瘸洛。
缺省情況下,所有支持動畫的屬性在動畫塊中發(fā)生的變化都會形成動畫次和。如果您希望讓動畫塊中發(fā)生的某些變化不產(chǎn)生動畫效果反肋,可以通過setAnimationsEnabled:
方法來暫時禁止動畫,在完成修改后才重新激活動畫踏施。在調(diào)用setAnimationsEnabled:
方法并傳入NO值之后石蔗,所有的改變都不會產(chǎn)生動畫效果,直到用YES值再次調(diào)用這個方法或者提交整個動畫塊時读规,動畫才會恢復(fù)抓督。您可以用areAnimationsEnabled
方法來確定當(dāng)前是否激活動畫。
配置動畫的委托
您可以為動畫塊分配一個委托束亏,并通過該委托接收動畫開始和結(jié)束的消息铃在。當(dāng)您需要在動畫開始前和結(jié)束后立即執(zhí)行其它任務(wù)時,可能就需要這樣做碍遍。您可以通過UIView的setAnimationDelegate:類方法來設(shè)置委托定铜,并通過setAnimationWillStartSelector:
和setAnimationDidStopSelector:
方法來指定接收消息的選擇器方法。消息處理方法的形式如下:
- (void)animationWillStart:(NSString *)animationID context:(void *)context;
- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context;
上面兩個方法的animationID和context參數(shù)和動畫塊開始時傳給beginAnimations:context:
方法的參數(shù)相同:
- animationID - 應(yīng)用程序提供的字符串怕敬,用于標(biāo)識一個動畫塊中的動畫揣炕。
- context - 也是應(yīng)用程序提供的對象,用于向委托對象傳遞額外的信息东跪。
setAnimationDidStopSelector:
選擇器方法還有一個參數(shù)—即一個布爾值畸陡。如果動畫順利完成,沒有被其它動畫取消或停止虽填,則該值為YES丁恭。
響應(yīng)布局的變化
任何時候,當(dāng)視圖的布局發(fā)生改變時斋日,UIKit會激活每個視圖的自動尺寸調(diào)整行為牲览,然后調(diào)用各自的layoutSubviews
方法,使您有機(jī)會進(jìn)一步調(diào)整子視圖的幾何尺寸恶守。下面列舉的情形都會引起視圖布局的變化:
- 視圖邊界矩形的尺寸發(fā)生變化第献。
- 滾動視圖的內(nèi)容偏移量—也就是可視內(nèi)容區(qū)域的原點—發(fā)生變化贡必。
- 和視圖關(guān)聯(lián)的轉(zhuǎn)換矩陣發(fā)生變化。
- 和視圖層相關(guān)聯(lián)的Core Animation子層組發(fā)生變化庸毫。
- 您的應(yīng)用程序調(diào)用視圖的
setNeedsLayout
或layoutIfNeeded
方法來強(qiáng)制進(jìn)行布局仔拟。 - 您的應(yīng)用程序調(diào)用視圖背后的層對象的
setNeedsLayout
方法來強(qiáng)制進(jìn)行布局。
子視圖的初始布局由視圖的自動尺寸調(diào)整行為來負(fù)責(zé)岔绸。應(yīng)用這些行為可以保證您的視圖接近其設(shè)計的尺寸理逊。有關(guān)自動尺寸調(diào)整行為如何影響視圖的尺寸和位置的更多信息,請參見“自動尺寸調(diào)整行為”部分盒揉。
有些時候晋被,您可能希望通過layoutSubviews
方法來手工調(diào)整子視圖的布局,而不是完全依賴自動尺 寸調(diào)整行為刚盈。舉例來說羡洛,如果您要實現(xiàn)一個由幾個子視圖元素組成的定制控件,則可以通過手工調(diào)整子視圖來精確控制控件在一定尺寸范圍內(nèi)的外觀藕漱。還有欲侮,如果一 個視圖表示的滾動內(nèi)容區(qū)域很大,可以選擇將內(nèi)容顯示為一組平鋪的子視圖肋联,在滾動過程中威蕉,可以回收離開屏幕邊界的視圖,并在填充新內(nèi)容后將它重新定位橄仍,使它 成為下一個滾入屏幕的視圖韧涨。
請注意:您也可以用layoutSubviews
方法來調(diào)整作為子層鏈接到視圖層的定制CALayer對象。您可以通過對隱藏在視圖后面的層層次進(jìn)行管理侮繁,實現(xiàn)直接基于Core Animation的高級動畫虑粥。有關(guān)如何通過Core Animation管理層層次的更多信息,請參見Core Animation編程指南宪哩。
在編寫布局代碼時娩贷,請務(wù)必在應(yīng)用程序支持的每個方向上都進(jìn)行測試。對于同時支持景觀方向和肖像方向的應(yīng)用程序锁孟,必須確認(rèn)其是否能正確處 理兩個方向上的布局彬祖。類似地,您的應(yīng)用程序應(yīng)該做好處理其它系統(tǒng)變化的準(zhǔn)備品抽,比如狀態(tài)條高度的變化涧至,如果用戶在使用您的應(yīng)用程序的同時接聽電話,然后再掛 斷桑包,就會發(fā)生這種變化。在掛斷時纺非,負(fù)責(zé)管理視圖的視圖控制器可能會調(diào)整視圖的尺寸哑了,以適應(yīng)縮小的狀態(tài)條赘方。之后,這樣的變化會向下滲透到應(yīng)用程序的其它視圖弱左。
重畫視圖的內(nèi)容
有些時候窄陡,應(yīng)用程序數(shù)據(jù)模型的變化會影響到相應(yīng)的用戶界面。為了反映這些變化拆火,您可以將相應(yīng)的視圖標(biāo)識為需要刷新(通過調(diào)用setNeedsDisplay
或setNeedsDisplayInRect:
方法)跳夭。和簡單創(chuàng)建一個圖形上下文并進(jìn)行描畫相比,將視圖標(biāo)識為需要刷新的方法使系統(tǒng)有機(jī)會更有效地執(zhí)行描畫操作们镜。舉例來說币叹,如果您在某個運行周期中將一個視圖的幾個區(qū)域標(biāo)識為需要刷新,系統(tǒng)就會將這些需要刷新的區(qū)域進(jìn)行合并模狭,并最終形成一個drawRect:
方法的調(diào)用颈抚。結(jié)果,只需要創(chuàng)建一個圖形上下文就可以描畫所有這些受影響的區(qū)域嚼鹉。這個做法比連續(xù)快速創(chuàng)建幾個圖形上下文要有效得多贩汉。
實現(xiàn)drawRect:
方法的視圖總是需要檢查傳入的矩形參數(shù),并用它來限制描畫操作的范圍锚赤。因為描畫是開銷相對昂貴的操作匹舞,以這種方式來限制描畫是提高性能的好方法。
缺省情況下线脚,視圖在幾何上的變化并不自動導(dǎo)致重畫赐稽。相反,大多數(shù)幾何變化都由Core Animation來自動處理酒贬。具體來說又憨,當(dāng)您改變視圖的frame、bounds锭吨、center蠢莺、或transform屬 性時,Core Animation會將相應(yīng)的幾何變化應(yīng)用到與視圖層相關(guān)聯(lián)的緩存位圖上零如。在很多情況下躏将,這種方法是完全可以接受的,但是如果您發(fā)現(xiàn)結(jié)果不是您期望得到 的考蕾,則可以強(qiáng)制UIKit對視圖進(jìn)行重畫祸憋。為了避免Core Animation自動處理幾何變化,您可以將視圖的contentMode屬性聲明設(shè)置為UIViewContentModeRedraw肖卧。更多有關(guān)內(nèi)容模式的信息蚯窥,請參見“內(nèi)容模式和比例縮放”部分。
隱藏視圖
您可以通過改變視圖的hidden屬性聲明來隱藏或顯示視圖。將這個屬性設(shè)置為YES會隱藏視圖拦赠,設(shè)置為NO則可以顯示視圖巍沙。對一個視圖進(jìn)行隱藏會同時隱藏其內(nèi)嵌的所有子視圖,就好象它們自己的hidden屬性也被設(shè)置一樣荷鼠。
當(dāng)您隱藏一個視圖時句携,該視圖仍然會保留在視圖層次中,但其內(nèi)容不會被描畫允乐,也不會接收任何觸摸事件矮嫉。由于隱藏視圖仍然存在于視圖層次 中,所以會繼續(xù)參與自動尺寸調(diào)整和其它布局操作牍疏。如果被隱藏的視圖是當(dāng)前的第一響應(yīng)者蠢笋,則該視圖會自動放棄其自動響應(yīng)者的狀態(tài),但目標(biāo)為第一響應(yīng)者的事件 仍然會傳遞給隱藏視圖麸澜。有關(guān)響應(yīng)者鏈的更多信息挺尿,請參見“響應(yīng)者對象和響應(yīng)者鏈”部分。
創(chuàng)建一個定制視圖
UIView類為在屏幕上顯示內(nèi)容及處理觸摸事件提供了潛在的支持炊邦,但是除了在視圖區(qū)域內(nèi)描畫帶有alpha值的背景色之外编矾,UIView類的實例不做其它描畫操作,包括其子視圖的描畫馁害。如果您的應(yīng)用程序需要顯示定制的內(nèi)容窄俏,或以特定的方式處理觸摸事件,必須創(chuàng)建UIView的定制子類碘菜。
本章的下面部分將描述一些定制視圖對象可能需要實現(xiàn)的關(guān)鍵方法和行為凹蜈。有關(guān)子類化的更多信息,請參見UIView類參考忍啸。
初始化您的定制視圖
您定義的每個新的視圖對象都應(yīng)該包含initWithFrame:
初始化方法仰坦。該方法負(fù)責(zé)在創(chuàng)建對象時對類進(jìn)行初始化,使之處于已知的狀態(tài)计雌。在通過代碼創(chuàng)建您的視圖實例時悄晃,需要使用這個方法。
程序清單2顯示了標(biāo)準(zhǔn)的initWithFrame:
方法的一個框架實現(xiàn)凿滤。該實現(xiàn)首先調(diào)用繼承自超類的實現(xiàn)妈橄,然后初始化類的實例變量和狀態(tài)信息,最后返回初始化完成的對象翁脆。您通常需要首先執(zhí)行超類的實現(xiàn)眷蚓,以便在出現(xiàn)問題時可以簡單地終止自己的初始化代碼,返回nil反番。
程序清單2 初始化一個視圖的子類
- (id)initWithFrame:(CGRect)aRect {
self = [super initWithFrame:aRect];
if (self) {
// setup the initial properties of the view
...
}
return self;
}
如果您從nib文件中裝載定制視圖類的實例沙热,則需要知道:在iPhone OS中叉钥,裝載nib的代碼并不通過initWithFrame:
方法來實例化新的視圖對象,而是通過NSCoding協(xié)議定義的initWithCoder:
方法來進(jìn)行篙贸。
即使您的視圖采納了NSCoding協(xié)議沼侣,Interface Builder也不知道它的定制屬性,因此不知道如何將那些屬性編碼到nib文件中。所以,當(dāng)您從nib文件裝載定制視圖時剧劝,initWithCoder:
方法不具有進(jìn)行正確初始化所需要的信息戒悠。為了解決這個問題,您可以在自己的類中實現(xiàn)awakeFromNib
方法钞螟,特別用于從nib文件裝載的定制類兔甘。
描畫您的視圖內(nèi)容
當(dāng)您改變視圖內(nèi)容時,可以通過setNeedsDisplay
或setNeedsDisplayInRect:
方法來將需要重畫的部分通知給系統(tǒng)鳞滨。在應(yīng)用程序返回運行循環(huán)之后洞焙,會對所有的描畫請求進(jìn)行合并,計算界面中需要被更新的部分拯啦;之后就開始遍歷視圖層次澡匪,向需要更新的視圖發(fā)送drawRect:
消息。遍歷的起點是視圖層次的根視圖褒链,然后從后往前遍歷其子視圖唁情。在可視邊界內(nèi)顯示定制內(nèi)容的視圖必須實現(xiàn)其drawRect:
方法,以便對該內(nèi)容進(jìn)行渲染甫匹。
在調(diào)用視圖的drawRect:
方法之前甸鸟,UIKit會為其配置描畫的環(huán)境,即創(chuàng)建一個圖形上下文兵迅,并調(diào)整其坐標(biāo)系統(tǒng)和裁剪區(qū)抢韭,使之和視圖的坐標(biāo)系統(tǒng)及邊界相匹配。因此恍箭,在您的drawRect:
方法被調(diào)用時刻恭,您可以使用UIKit的類和函數(shù)、Quartz的函數(shù)季惯、或者使用兩者相結(jié)合的方法來直接進(jìn)行描畫吠各。需要的話,您可以通過UIGraphicsGetCurrentContext
函數(shù)來取得當(dāng)前圖形上下文的指針勉抓,實現(xiàn)對它的訪問贾漏。
重要提示:只有當(dāng)定制視圖的drawRect:
方法被調(diào)用的期間,當(dāng)前圖形上下文才是有效的藕筋。UIKit可能為該方法的每個調(diào)用創(chuàng)建不同的圖形上下文纵散,因此,您不應(yīng)該對該對象進(jìn)行緩存并在之后使用。
程序清單3顯示了drawRect:
方法的一個簡單實現(xiàn)伍掀,即在視圖邊界描畫一個10像素寬的紅色邊界掰茶。由于UIKit描畫操作的實現(xiàn)也是基于Quartz,所以您可以像下面這樣混合使用不同的描畫調(diào)用來得到期望的結(jié)果蜜笤。
程序清單3 一個描畫方法
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect myFrame = self.bounds;
CGContextSetLineWidth(context, 10);
[[UIColor redColor] set];
UIRectFrame(myFrame);
}
如果您能確定自己的描畫代碼總是以不透明的內(nèi)容覆蓋整個視圖的表面濒蒋,則可以將視圖的opaque屬性聲明設(shè)置為YES,以提高描畫代碼的總體效率把兔。當(dāng)您將視圖標(biāo)識為不透明時沪伙,UIKit會避免對該視圖正下方的內(nèi)容進(jìn)行描畫。這不僅減少了描畫開銷的時間县好,而且減少內(nèi)容合成需要的工作围橡。然而,只有當(dāng)您能確定視圖提供的內(nèi)容為不透明時缕贡,才能將這個屬性設(shè)置為YES翁授;如果您不能保證視圖內(nèi)容總是不透明,則應(yīng)該將它設(shè)置為NO晾咪。
提高描畫性能(特別是在滾動過程)的另一個方法是將視圖的clearsContextBeforeDrawing屬性設(shè)置為NO收擦。當(dāng)這個屬性被設(shè)置為YES時,UIKIt會在調(diào)用drawRect:方法之前禀酱,把即將被該方法更新的區(qū)域填充為透明的黑色炬守。將這個屬性設(shè)置為NO可以取消相應(yīng)的填充操作,而由應(yīng)用程序負(fù)責(zé)完全重畫傳給drawRect:方法的更新矩形中的部分剂跟。這樣的優(yōu)化在滾動過程中通常是一個好的折衷减途。
響應(yīng)事件
UIView類是UIResponder的一個子類,因此能夠接收用戶和視圖內(nèi)容交互時產(chǎn)生的觸摸事件曹洽。觸摸事件從發(fā)生觸摸的視圖開始鳍置,沿著響應(yīng)者鏈進(jìn)行傳遞,直到最后被處理送淆。視圖本身就是響應(yīng)者税产,是響應(yīng)者鏈的參與者,因此可以收到所有關(guān)聯(lián)子視圖派發(fā)給它們的觸摸事件偷崩。
處理觸摸事件的視圖通常需要實現(xiàn)下面的所有方法,更多細(xì)節(jié)請參見“事件處理”部分:
touchesBegan:withEvent:
touchesMoved:withEvent:
touchesEnded:withEvent:
touchesCancelled:withEvent:
請記住阐斜,在缺省情況下衫冻,視圖每次只響應(yīng)一個觸摸動作。如果用戶將第二個手指放在屏幕上谒出,系統(tǒng)會忽略該觸摸事件隅俘,而不會將它報告給視圖對象邻奠。如果您希望在視圖的事件處理器方法中跟蹤多點觸摸手勢,則需要重新激活多點觸摸事件为居,具體方法是將視圖的multipleTouchEnabled屬性聲明設(shè)置為YES碌宴。
某些視圖,比如標(biāo)簽和圖像視圖蒙畴,在初始狀態(tài)下完全禁止事件處理贰镣。您可以通過改變視圖的userInteractionEnabled屬性值來控制視圖是否可以對事件進(jìn)行處理。當(dāng)某個耗時很長的操作被掛起時膳凝,您可以暫時將這個屬性設(shè)置為NO八孝,使用戶無法對視圖的內(nèi)容進(jìn)行操作。為了阻止事件到達(dá)您的視圖鸠项,還可以使用UIApplication對象的beginIgnoringInteractionEvents和endIgnoringInteractionEvents方法。這些方法影響的是整個應(yīng)用程序的事件分發(fā)子姜,而不僅僅是某個視圖祟绊。
在處理觸摸事件時,UIKit會通過UIView的hitTest:withEvent:和pointInside:withEvent:方法來確定觸摸事件是否發(fā)生在指定的視圖上哥捕。雖然很少需要重載這些方法牧抽,但是您可以通過重載來使子視圖無法處理觸摸事件。
視圖對象的清理
如果您的視圖類分配了任何內(nèi)存遥赚、存儲了任何對象的引用扬舒、或者持有在釋放視圖時也需要被釋放的資源,則必須實現(xiàn)其dealloc方法凫佛。當(dāng)您的視圖對象的保持?jǐn)?shù)為零讲坎、且視圖本身即將被解除分配時,系統(tǒng)會調(diào)用其dealloc方法愧薛。您在這個方法的實現(xiàn)中應(yīng)該釋放視圖持有的對象和資源晨炕,然后調(diào)用超類的實現(xiàn),如程序程序清單2-4所示毫炉。
程序清單2-4 實現(xiàn)dealloc方法
- (void)dealloc {
// Release a retained UIColor object
[color release];
// Call the inherited implementation
[super dealloc];
}