iOS開發(fā)-探索scrollView的實現(xiàn)

序言

UIScrollView滾動視圖闲延,絕對算的上是iOS開發(fā)中最重要的控件,用來展示多于一個屏幕的內(nèi)容陆馁,可以滾動顯示超過屏幕外的內(nèi)容的特性使其產(chǎn)生了更多強大的子類:UITableView合愈、UICollectionView想暗、UITextView等等。盡管功能如此強大说莫,但是scrollView本質(zhì)上只是一個UIView的黑魔法储狭,本文將剖析UIScrollView這種強大特性的實現(xiàn)過程


圖層渲染

這里不得不提到UIView和CALayer的關(guān)系捣郊。在UIKit框架中慈参,UIView是所有界面元素的基礎(chǔ)驮配,我們頁面上可見的控件幾乎都是從這個類派生出來的。之所以說幾乎意味著我們也可以不通過UIView及其子類的途徑來展示一些頁面效果琐旁,比如有漸變效果的進度條——通過CALayer直接完成猜绣。關(guān)于兩者的具體區(qū)別以及關(guān)系,我們不在這里詳說牺陶,只需要知道每一個UIView管理著一個CALayer辣之,所有我們看到的內(nèi)容都是由后者進行渲染的。
當(dāng)我們添加子視圖的時候碱工,會基于當(dāng)前視圖的坐標(biāo)系原點進行計算奏夫,然后在設(shè)置好的位置對子視圖的layer進行渲染酗昼,假設(shè)現(xiàn)在添加一個frame為40, 40, 120, 40的按鈕梳猪,那么渲染圖示如下:

1.png

在按鈕添加到當(dāng)前視圖之前春弥,按鈕自身先進行了渲染,然后在距離父視圖上邊40點扫责,左邊40點的位置進行組合逃呼。正是因為這種組合的渲染模式者娱,在頂層的視圖總是會遮蓋下層的視圖黄鳍。通過下面的視圖組合流程平匈,我們也能明白為什么創(chuàng)建的view的bounds總是{0, 0, width, height}

1.png

根據(jù)上面的視圖組合增炭,我們試想一下,如果button的坐標(biāo)是(100, 40)弟跑,那么這個按鈕還會顯示在view上面嗎孟辑?答案是肯定的,因為根據(jù)上面視圖組合的實現(xiàn)炭玫,我們可以得出一個結(jié)論:當(dāng)前的視圖也存在一個父視圖貌虾,父視圖也存在其所在的父視圖。如此循環(huán)衔憨,直到這個視圖是keyWindow為止袄膏。那么我們就有下面的結(jié)構(gòu)圖示

1.png

因此按鈕是處在我們可視的范圍內(nèi)的沉馆。但是,按照這種組合方式揖盘,scrollView的實現(xiàn)就顯得非常的神奇了锌奴,因為在scrollView上面的子視圖一旦超過了它的顯示范圍。這里需要說到view的clipsToBoundslayer的maskToBounds屬性椭符,這兩個屬性盡管名字不一樣,但是如果你在堆棧調(diào)用的時候進行調(diào)試有咨,會發(fā)現(xiàn)最終調(diào)用的是maskToBounds方法蒸健。這兩個值任意一個設(shè)置YES的時候似忧,在上面視圖組合的③步驟中,超出父視圖范圍內(nèi)的部分將不進行渲染淳衙。
那么scrollView是否跟我們猜測的一樣饺著,通過設(shè)置maskToBounds這個值來屏蔽超出其顯示范圍的子視圖呢幼衰?如果是的話,那么scrollView就只是一個普通的UIView渡嚣。我們通過下面的代碼驗證

UIScrollView * scrollView = [[UIScrollView alloc] initWithFrame: CGRectMake(0, 0, 200, 180)];
scrollView.backgroundColor = [UIColor orangeColor];
scrollView.center = self.view.center;
[self.view addSubview: scrollView];

UIView * subview = [[UIView alloc] initWithFrame: CGRectMake(60, 60, 180, 180)];
subview.backgroundColor = [UIColor blueColor];
[scrollView addSubview: subview];

這時候scrollView的效果是這樣的:


1.png

接下來設(shè)置scrollView的maskToBounds屬性

 scrollView.layer.masksToBounds = NO;

效果圖


1.png

可以看到绝葡,scrollView本質(zhì)上不過是一個默認遮蓋范圍外子視圖的UIView罷了腹鹉。那么种蘸,UIView到底使用了什么黑魔法來實現(xiàn)滾動視圖呢竞膳?


contentOffset

用過scrollView的開發(fā)者對這個屬性都不陌生,contentOffset決定了當(dāng)前scrollView顯示內(nèi)容的范圍刊侯,即是當(dāng)前scrollView的左上角的顯示位置坐標(biāo)锉走。通過圖片輪播控件來探究這個屬性的實現(xiàn)

1.png

上圖中scrollView發(fā)生了滾動,使得顯示的圖片從1變成2休偶。在這個過程中辜羊,contentOffset也從(0, 0)變?yōu)?width, 0) 從這張圖上看更像是子視圖的位置發(fā)生了移動,從右向左移動碱妆。但是在這一切發(fā)生的過程中昔驱,子視圖的frame沒有發(fā)生過任何變化,因此與其說是滾動纳本,不如說是scrollView基于子視圖的所在的坐標(biāo)系發(fā)生了偏移:


1.png

兩張圖都表示了圖片輪播的過程饮醇,但是第二張更加接近scrollView滾動實現(xiàn)的本質(zhì)——基于自身的坐標(biāo)系發(fā)生了位置偏移秕豫。因此混移,contentOffset實際上表示的是scrollView的bounds的改變,其實現(xiàn)大概如下

 - (void)setContentOffset: (CGPoint)contentOffset
 {
    _contentOffset = contentOffset;
    CGRect bounds = self.bounds;
    bounds.origin = contentOffset;
    self.bounds = bounds;
 }

contentSize

如果說contentOffset決定了scrollView的窗口毁嗦,那么contentSize決定了這個窗口背后的風(fēng)光回铛。

1.png

contentSize決定了scrollView顯示內(nèi)容的尺寸范圍茵肃,從上圖看,我們可以知道捞附,在contentSize的寬度或者長度任意一個尺寸大于scrollView等邊長度的時候,scrollView才能實現(xiàn)滾動效果胆绊。當(dāng)然了欧募,單單是contentSize是不足以讓我們實現(xiàn)scrollView的滾動范圍限制的,這是contentSizecontentOffset的共同實現(xiàn)效果:
1.png


contentInset

contentInset是一個相當(dāng)有用的屬性何缓,我在做的一個毛玻璃效果導(dǎo)航欄上下拉效果時就通過這個屬性實現(xiàn)碌廓。這個屬性可以在某種意義上增加或者減少我們的滾動尺寸范圍:

1.png

可以看到谷婆,contentInset讓我們原本contentOffsetcontentSize協(xié)同作用的滾動范圍發(fā)生了改變辽聊,原本最上角(0, 0)的限制坐標(biāo)變成了(-contentInset.left, -contentInset.top)
既然contentInset只是簡單的改變了滾動范圍的規(guī)則,為什么我們不直接通過contentSize來實現(xiàn)呢异袄?這是由于更多時間玛臂,我們還需要在滾動視圖的某個方向上面留下一塊空白的區(qū)域進行自定義,這時候直接設(shè)置contentInset是最快的方式讽营。而換成contentSize來實現(xiàn)泡徙,我們還必須同時改變bounds跟center來實現(xiàn)(不要直接改變frame堪藐,在組合視圖時,frame最后是由bounds和center決定的)


尾話

蘋果對于scrollView的實現(xiàn)十分的巧妙糖荒,在沒有造成過多損耗的情況下賦予UIView一份強大無比的力量苏章。解剖UIKit不僅僅是為了探索實現(xiàn),這對于我們自定義控件能有更多的認識泉孩。在scrollView更上一層的UITableView通過復(fù)用隊列的方式將scrollView的能力更加完美的展示出來并淋,而這個復(fù)用機制值得我們?nèi)ニ伎紝崿F(xiàn)過程县耽。本文demo
文集:iOS開發(fā)

轉(zhuǎn)載注明鏈接:探索UIScrollView的實現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末兔毙,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子锡溯,更是在濱河造成了極大的恐慌哑姚,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件倡蝙,死亡現(xiàn)場離奇詭異寺鸥,居然都是意外死亡征炼,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酸些,“玉大人,你說我怎么就攤上這事沿侈∈欣酰” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵咙好,是天一觀的道長褐荷。 經(jīng)常有香客問我,道長层宫,這世上最難降的妖魔是什么其监? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任棠赛,我火速辦了婚禮,結(jié)果婚禮上鼎俘,老公的妹妹穿的比我還像新娘辩涝。我一直安慰自己怔揩,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布伏伐。 她就那樣靜靜地躺著藐翎,像睡著了一般实幕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上末贾,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天拱撵,我揣著相機與錄音,去河邊找鬼拴测。 笑死昼扛,一個胖子當(dāng)著我的面吹牛欲诺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蛹含,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼浦箱,長吁一口氣:“原來是場噩夢啊……” “哼祠锣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起伴网,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤澡腾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后毅糟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體澜公,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡玛瘸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年糊渊,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贺喝。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡躏鱼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鹊漠,到底是詐尸還是另有隱情茶行,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布娶靡,位于F島的核電站看锉,受9級特大地震影響伯铣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜腔寡,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一蹬蚁、第九天 我趴在偏房一處隱蔽的房頂上張望犀斋。 院中可真熱鬧,春花似錦叽粹、人聲如沸虫几。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辆脸。三九已至但校,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間啡氢,已是汗流浹背状囱。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工术裸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人亭枷。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓袭艺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親叨粘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

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