国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計(jì) > 正文

理解Scroll View

2019-11-09 17:33:39
字體:
供稿:網(wǎng)友

可能你很難相信,UIScrollView和一個(gè)標(biāo)準(zhǔn)的UIView差異并不大,scroll view確實(shí)會(huì)多一些方法,但這些方法只是UIView一些屬性的表面而已。因此,要想弄懂UIScrollView是怎么工作之前,你需要了解UIView,特別是視圖渲染過程的兩步。

光柵化和組合

渲染過程的第一部分是眾所周知的光柵化,光柵化簡單的說就是產(chǎn)生一組繪圖指令并且生成一張圖片。比如繪制一個(gè)圓角矩形、帶圖片、標(biāo)題居中的UIButtons。這些圖片并沒有被繪制到屏幕上去;取而代之的是,他們被自己的視圖保持著留到下一個(gè)步驟用。

一旦每個(gè)視圖都產(chǎn)生了自己的光柵化圖片,這些圖片便被一個(gè)接一個(gè)的繪制,并產(chǎn)生一個(gè)屏幕大小的圖片,這便是上文所說的組合。視圖層級(jí)(view hierarchy)對于組合如何進(jìn)行扮演了很重要的角色:一個(gè)視圖的圖片被組合在它父視圖圖片的上面。然后,組合好的圖片被組合到父視圖的父視圖圖片上面,就這樣。。。最終視圖層級(jí)最頂端是窗口(window),它組合好的圖片便是我們看到的東西了。

概念上,依次在每個(gè)視圖上放置獨(dú)立分層的圖片并最終產(chǎn)生一個(gè)圖片,單調(diào)的圖像將會(huì)變得更容易理解,特別是如果你以前使用過像photoshop這樣的工具。我們還有另外一篇文章詳細(xì)解釋了像素是如何繪制到屏幕上去的。

現(xiàn)在,回想一下,每個(gè)視圖都有一個(gè)bounds和frame。當(dāng)布局一個(gè)界面時(shí),我們需要處理視圖的frame。這允許我們放置并設(shè)置視圖的大小。視圖的frame和bounds的大小總是一樣的,但是他們的origin有可能不同。弄懂這兩個(gè)工作原理是理解UIScrollView的關(guān)鍵。

在光柵化步驟中,視圖并不關(guān)心即將發(fā)生的組合步驟。也就是說,它并不關(guān)心自己的frame(這是用來放置視圖的圖像)或自己在視圖層級(jí)中的位置(這是決定組合的順序)。這時(shí)視圖只關(guān)心一件事就是繪制它自己的content。這個(gè)繪制發(fā)生在每個(gè)視圖的drawRect:方法中。

在drawRect:方法被調(diào)用前,會(huì)為視圖創(chuàng)建一個(gè)空白的圖片來繪制content。這個(gè)圖片的坐標(biāo)系統(tǒng)是視圖的bounds。幾乎每個(gè)視圖bounds的origin都是{0,0}。因此,當(dāng)在刪格化圖片左上角繪制一些東西的時(shí)候,你都會(huì)在bounds的origin({x:0,y:0})處繪制。在一個(gè)圖片右下角的地方繪制東西的時(shí)候,你都會(huì)繪制在{x:width, y:height}處。如果你的繪制超出了視圖的bounds,那么超出的部分就不屬于刪格化圖片的部分了,并且會(huì)被丟棄。

在       組合的步驟中,每個(gè)視圖將自己光柵化圖片組合到自己父視圖的光柵化圖片上面。視圖的frame決定了自己在父視圖中繪制的位置,frame的origin表明了視圖光柵化圖片左上角相對父視圖光柵化圖片左上角的偏移量。所以,一個(gè)origin為{x:20,y:15}的frame所繪制的圖片左邊距其父視圖20點(diǎn),上邊距父視圖15點(diǎn)。因?yàn)橐晥D的frame和bounds矩形的大小總是一樣的,所以光柵化圖片組合的時(shí)候是像素對齊的。這確保了光柵化圖片不會(huì)被拉伸或縮小。

記住,我們才僅僅討論了一個(gè)視圖和它父視圖之間的組合操作。一旦這兩個(gè)視圖被組合到一起,組合的結(jié)果圖片將會(huì)和父視圖的父視圖進(jìn)行組合。。。這是一個(gè)雪球效應(yīng)。

考慮一下組合圖片背后的公式。視圖圖片的左上角會(huì)根據(jù)它frame的origin進(jìn)行偏移,并繪制到父視圖的圖片上:

CompositedPosition.x = View.frame.origin.x - Superview.bounds.origin.x;   CompositedPosition.y = View.frame.origin.y - Superview.bounds.origin.y;我們可以通過幾個(gè)不同的frames看一下:

        這樣做是有道理的。我們改變button的frame.origin后,它會(huì)改變自己相對紫色父視圖的位置。注意,如果我們移動(dòng)button直到它的一部分已經(jīng)在紫色父視圖bounds的外面,當(dāng)光柵化圖片被截去時(shí)這部分也將會(huì)通過同樣的繪制方式被截去。然而,技術(shù)上講,因?yàn)閕OS處理組合方法的原因,你可以將一個(gè)子視圖渲染在其父視圖的bounds之外,但是光柵化期間的繪制不可能超出一個(gè)視圖的bounds。

Scroll View的Content Offset

現(xiàn)在,我們所講的跟UIScrollView有什么關(guān)系呢?一切都和它有關(guān)!考慮一種我們可以實(shí)現(xiàn)的滾動(dòng):我們有一個(gè)拖動(dòng)時(shí)frame不斷改變的視圖。這達(dá)到了相同的效果,對嗎?如果我拖動(dòng)我的手指到右邊,那么拖動(dòng)的同時(shí)我增大視圖的origin.x,瞧,這貨就是scroll view。

當(dāng)然,在scroll view中有很多具有代表性的視圖。為了實(shí)現(xiàn)這個(gè)平移功能,當(dāng)用戶移動(dòng)手指時(shí),你需要時(shí)刻改變每個(gè)視圖的frames。當(dāng)我們提出組合一個(gè)view的光柵化圖片到它父視圖什么地方時(shí),記住這個(gè)公式:

CompositedPosition.x = View.frame.origin.x - Superview.bounds.origin.x;   CompositedPosition.y = View.frame.origin.y - Superview.bounds.origin.y; 我們減少Superview.bounds.origin的值(因?yàn)樗麄兛偸?)。但是如果他們不為0呢?我們用和前一個(gè)圖例相同的frames,但是我們改變了紫色視圖bounds的origin為{-30,-30}。得到下圖:

現(xiàn)在,巧妙的是通過改變這個(gè)紫色視圖的bounds,它每一個(gè)單獨(dú)的子視圖都被移動(dòng)了。事實(shí)上,這正是一個(gè)scroll view工作的原理。當(dāng)你設(shè)置它的contentOffset屬性時(shí):它改變scroll view.bounds的origin。事實(shí)上,contentOffset甚至不是實(shí)際存在的。代碼看起來像這樣:

- (void)setContentOffset:(CGPoint)offset {     CGRect bounds = [self bounds];     bounds.origin = offset;     [self setBounds:bounds]; } 

注意:前一個(gè)圖例,只要足夠的改變bounds的origin,button將會(huì)超出紫色視圖和button組合成的圖片的范圍。這也是當(dāng)你足夠的移動(dòng)scroll view時(shí),一個(gè)視圖會(huì)消失!

世界之窗:Content Size

現(xiàn)在,最難的部分已經(jīng)過去了,我們再看看UIScrollView另一個(gè)屬性:contentSize。scroll view的content size并不會(huì)改變其bounds的任何東西,所以這并不會(huì)影響scroll view如何組合自己的子視圖。反而,content size定義了可滾動(dòng)區(qū)域。scroll view的默認(rèn)content size為{w:0,h:0}。既然沒有可滾動(dòng)區(qū)域,用戶是不可以滾動(dòng)的,但是scroll view任然會(huì)顯示其bounds范圍內(nèi)所有的子視圖。

 

當(dāng)content size設(shè)置為比bounds大的時(shí)候,用戶就可以滾動(dòng)視圖了。你可以認(rèn)為scroll view的bounds為可滾動(dòng)區(qū)域上的一個(gè)窗口:

當(dāng)content offset為{x:0,y:0}時(shí),可見窗口的左上角在可滾動(dòng)區(qū)域的左上角處。這也是content offset的最小值;用戶不能再往可滾動(dòng)區(qū)域的左邊或上邊移動(dòng)了。那兒沒啥,別滾了!

 

content offset的最大值是content size和scroll view size的差。這也在情理之中:從左上角一直滾動(dòng)到右下角,用戶停止時(shí),滾動(dòng)區(qū)域右下角邊緣和滾動(dòng)視圖bounds的右下角邊緣是齊平的。你可以像這樣記下content offset的最大值:

contentOffset.x = contentSize.width - bounds.size.width;   contentOffset.y = contentSize.height - bounds.size.height; 

 

用Content Insets對窗口稍作調(diào)整

contentInset屬性可以改變content offset的最大和最小值,這樣便可以滾動(dòng)出可滾動(dòng)區(qū)域。它的類型為UIEdgeInsets,包含四個(gè)值:{top,left,bottom,right}。當(dāng)你引進(jìn)一個(gè)inset時(shí),你改變了content offset的范圍。比如,設(shè)置content inset頂部值為10,則允許content offset的y值達(dá)到10。這介紹了可滾動(dòng)區(qū)域周圍的填充。

這咋一看好像沒什么用。實(shí)際上,為什么不僅僅增加content size呢?除非沒辦法,否則你需要避免改變scroll view的content size。想要知道為什么?想想一個(gè)table view(UItableView是UIScrollView的子類,所以它有所有相同的屬性),table view為了適應(yīng)每一個(gè)cell,它的可滾動(dòng)區(qū)域是通過精心計(jì)算的。當(dāng)你滾動(dòng)經(jīng)過table view的第一個(gè)或最后一個(gè)cell的邊界時(shí),table view將content offset彈回并復(fù)位,所以cells又一次恰到好處的緊貼scroll view的bounds。

 

當(dāng)你想要使用UIRefreshControl實(shí)現(xiàn)拉動(dòng)刷新時(shí)發(fā)生了什么?你不能在table view的可滾動(dòng)區(qū)域內(nèi)放置UIRefreshControl,否則,table view將會(huì)允許用戶通過refresh control中途停止?jié)L動(dòng),并且將refresh control的頂部彈回到視圖的頂部。因此,你必須將refresh control放在可滾動(dòng)區(qū)域上方。這將允許首先將content offset彈回第一行,而不是refresh control。

 

但是等等,如果你通過滾動(dòng)足夠多的距離初始化pull-to-refresh機(jī)制,因?yàn)閠able view設(shè)置了content inset,這將允許content offset將refresh control彈回到可滾動(dòng)區(qū)域。當(dāng)刷新動(dòng)作被初始化時(shí),content inset已經(jīng)被校正過,所以content offset的最小值包含了完整的refresh control。當(dāng)刷新完成后,content inset恢復(fù)正常,content offset也跟著適應(yīng)大小,這里并不需要為content size做數(shù)學(xué)計(jì)算。(這里可能比較難理解,建議看看EGOTableViewPullRefresh這樣的類庫就應(yīng)該明白了)

 

如何在自己的代碼中使用content inset?當(dāng)鍵盤在屏幕上時(shí),有一個(gè)很好的用途:你想要設(shè)置一個(gè)緊貼屏幕的用戶界面。當(dāng)鍵盤出現(xiàn)在屏幕上時(shí),你損失了幾百個(gè)像素的空間,鍵盤下面的東西全都被擋住了。

 

現(xiàn)在,scroll view的bounds并沒有改變,content size也并沒有改變(也不需要改變)。但是用戶不能滾動(dòng)scroll view。考慮一下之前一個(gè)公式:content offset的最大值并不同于content size和bounds的大小。如果他們相等,現(xiàn)在content offset的最大值是{x:0,y:0}.

 

現(xiàn)在開始出絕招,將界面放入一個(gè)scroll view。scroll view的content size仍然和scroll view的bounds一樣大。當(dāng)鍵盤出現(xiàn)在屏幕上時(shí),你設(shè)置content inset的底部等于鍵盤的高度。

這允許在content offset的最大值下顯示滾動(dòng)區(qū)域外的區(qū)域。可視區(qū)域的頂部在scroll view bounds的外面,因此被截取了(雖然它在屏幕之外了,但這并沒有什么)。

 

但愿這能讓你理解一些滾動(dòng)視圖內(nèi)部工作的原理,你對縮放感興趣?好吧,我們今天不會(huì)談?wù)撍沁@兒有一個(gè)有趣的小竅門:檢查viewForZoomingInScrollView:方法返回視圖的transform屬性。你將再次發(fā)現(xiàn)scroll view只是聰明的利用了UIView已經(jīng)存在的屬性。


發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 尼勒克县| 延津县| 深州市| 鄱阳县| 丽江市| 湟中县| 宜宾县| 汉寿县| 吐鲁番市| 沁阳市| 巴南区| 伊吾县| 于都县| 阿尔山市| 晋州市| 太白县| 锦州市| 江山市| 大余县| 阜康市| 石嘴山市| 长宁县| 大埔县| 吉隆县| 虞城县| 西乌珠穆沁旗| 香格里拉县| 洪江市| 贵南县| 永定县| 岳普湖县| 太湖县| 永川市| 乌鲁木齐县| 广东省| 孝义市| 伊吾县| 枝江市| 金寨县| 清流县| 正安县|