理解 Scroll Views

原文?理解 Scroll Views

可能你很難相信 UIScrollView 和一個(gè)標(biāo)準(zhǔn)的 UIView 差異并不大屁奏,scroll view 確實(shí)會(huì)多出一些方法,但這些方法只是和 UIView 的屬性很好的結(jié)合到一起了饼煞。因此,在要想弄懂 UIScrollView 是怎么工作之前泣特,你需要先了解一下 UIView基显,特別是視圖渲染的兩步過(guò)程。

光柵化和組合

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

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

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

現(xiàn)在焦履,回想一下劣欢,每個(gè)視圖都有一個(gè) bounds 和 frame。當(dāng)布局一個(gè)界面時(shí)裁良,我們需要處理視圖的 frame。這允許我們放置并設(shè)置視圖的大小校套。視圖的 frame 和 bounds 的大小通常是一樣的(雖然可以被 transforms 改變)价脾,但是他們的 origin 經(jīng)常是不同的。弄懂這兩個(gè)工作原理是理解 UIScrollView 的關(guān)鍵笛匙。

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

在 drawRect: 方法被調(diào)用前骇笔,會(huì)為視圖創(chuàng)建一個(gè)空白的圖片來(lái)繪制 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 表明了視圖光柵化圖片左上角相對(duì)父視圖光柵化圖片左上角的偏移量烛愧。所以,一個(gè) origin 為 {x:20, y:15} 的 frame 所繪制的圖片左邊距其父視圖 20 點(diǎn)掂碱,上邊距父視圖 15 點(diǎn)怜姿。因?yàn)橐晥D的 frame 和 bounds 矩形的大小總是一樣的,所以光柵化圖片組合的時(shí)候是像素對(duì)齊的疼燥。這確保了光柵化圖片不會(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;

正如之前所說(shuō)的剥槐,如果一個(gè)視圖 bounds 的 origin 是 {0,0}唱歧。那么,我們得到這個(gè)公式:

CompositedPosition.x = View.frame.origin.x;

CompositedPosition.y = View.frame.origin.y;

我們可以通過(guò)幾個(gè)不同的 frames 看一下:

這樣做是有道理的粒竖,我們改變 button 的 frame.origin后颅崩,它會(huì)改變自己相對(duì)紫色父視圖的位置。注意蕊苗,如果我們移動(dòng) button 直到它的一部分已經(jīng)在紫色父視圖 bounds 的外面沿后,當(dāng)光柵化圖片被截去時(shí)這部分也將會(huì)通過(guò)同樣的繪制方式被截去。然而朽砰,技術(shù)上講尖滚,因?yàn)?iOS 處理組合方法的原因,你可以將一個(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á)到了相同的效果,對(duì)嗎备绽?如果我拖動(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)在,巧妙的是通過(guò)改變這個(gè)紫色視圖的 bounds呢撞,它每一個(gè)單獨(dú)的子視圖都被移動(dòng)了损姜。事實(shí)上,這正是 scroll view 工作的原理殊霞。當(dāng)你設(shè)置它的 contentOffset 屬性時(shí)它改變 scroll view.bounds 的 origin摧阅。事實(shí)上,contentOffset 甚至不是實(shí)際存在的绷蹲。代碼看起來(lái)像這樣:

- (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)過(guò)去了,我們?cè)倏纯?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}。既然沒(méi)有可滾動(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í)监徘,可見(jiàn)窗口的左上角在可滾動(dòng)區(qū)域的左上角處。這也是 content offset 的最小值吧碾;用戶不能再往可滾動(dòng)區(qū)域的左邊或上邊移動(dòng)了凰盔。那兒沒(méi)啥,別滾了倦春!

content offset 的最大值是 content size 和 scroll view size 的差(不同于 content size 和scroll view的 bounds 大小)户敬。這也在情理之中:從左上角一直滾動(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 對(duì)窗口稍作調(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ū)域周圍的填充。

這咋一看好像沒(méi)什么用来颤。實(shí)際上汰扭,為什么不僅僅增加 content size 呢?除非沒(méi)辦法福铅,否則你需要避免改變scroll view 的 content size萝毛。想要知道為什么?想想一個(gè) table view(UItableView是UIScrollView 的子類滑黔,所以它有所有相同的屬性)笆包,table view 為了適應(yīng)每一個(gè)cell,它的可滾動(dòng)區(qū)域是通過(guò)精心計(jì)算的略荡。當(dāng)你滾動(dòng)經(jīng)過(guò) 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ì)允許用戶通過(guò) refresh control 中途停止?jié)L動(dòng)验毡,并且將 refresh control 的頂部彈回到視圖的頂部衡创。因此,你必須將 refresh control 放在可滾動(dòng)區(qū)域上方晶通。這將允許首先將 content offset 彈回第一行璃氢,而不是 refresh control。

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

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

現(xiàn)在怎棱,scroll view 的 bounds 并沒(méi)有改變哩俭,content size 也并沒(méi)有改變(也不需要改變)。但是用戶不能滾動(dòng) scroll view拳恋》沧剩考慮一下之前一個(gè)公式:content offset 的最大值是 content size 和 bounds 的差。如果他們相等诅岩,現(xiàn)在 content offset 的最大值是 {x:0, y:0}.

現(xiàn)在開(kāi)始出絕招,將界面放入一個(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 的外面芭挽,因此被截取了(雖然它在屏幕之外了滑废,但這并沒(méi)有什么)蝗肪。

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

計(jì)算機(jī)圖形渲染的流程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市腊状,隨后出現(xiàn)的幾起案子诱咏,更是在濱河造成了極大的恐慌,老刑警劉巖缴挖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件袋狞,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡映屋,警方通過(guò)查閱死者的電腦和手機(jī)苟鸯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)秧荆,“玉大人倔毙,你說(shuō)我怎么就攤上這事∫冶簦” “怎么了陕赃?”我有些...
    開(kāi)封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)颁股。 經(jīng)常有香客問(wèn)我么库,道長(zhǎng),這世上最難降的妖魔是什么甘有? 我笑而不...
    開(kāi)封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任诉儒,我火速辦了婚禮,結(jié)果婚禮上亏掀,老公的妹妹穿的比我還像新娘忱反。我一直安慰自己,他們只是感情好滤愕,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布温算。 她就那樣靜靜地躺著,像睡著了一般间影。 火紅的嫁衣襯著肌膚如雪注竿。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音巩割,去河邊找鬼裙顽。 笑死,一個(gè)胖子當(dāng)著我的面吹牛宣谈,可吹牛的內(nèi)容都是我干的愈犹。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蒲祈,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼甘萧!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起梆掸,我...
    開(kāi)封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤扬卷,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后酸钦,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體怪得,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年卑硫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了徒恋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡欢伏,死狀恐怖入挣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情硝拧,我是刑警寧澤径筏,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站障陶,受9級(jí)特大地震影響滋恬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜抱究,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一恢氯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鼓寺,春花似錦勋拟、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至州丹,卻和暖如春醋安,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背墓毒。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工吓揪, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人所计。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓柠辞,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親主胧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子叭首,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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

  • 可能你很難相信 UIScrollView 和一個(gè)標(biāo)準(zhǔn)的 UIView 差異并不大,scroll view 確實(shí)會(huì)多...
    燃燒的木頭閱讀 554評(píng)論 2 1
  • 可能你很難相信UIScrollView和一個(gè)標(biāo)準(zhǔn)的UIView差異并不大踪栋,scroll view 確實(shí)會(huì)多出一些方...
    評(píng)評(píng)分分閱讀 678評(píng)論 2 9
  • 原文鏈接:Understanding Scroll Views 可能你很難相信焙格,UIScrollView和一個(gè)標(biāo)準(zhǔn)...
    nadou23閱讀 397評(píng)論 0 0
  • 自己簡(jiǎn)單總結(jié): 點(diǎn)就在理解UIView渲染的subview 光柵化圖片時(shí)候組合的過(guò)程 公式: Composited...
    大餅炒雞蛋閱讀 294評(píng)論 0 1
  • Objc中國(guó) #3 Views 1. 繪制像素到屏幕上 Display 的上一層便是圖形處理單元 GPU,GPU ...
    _淺墨_閱讀 1,425評(píng)論 0 2