在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ iOS/ 理解 Scroll Views
與四軸無人機的通訊
在沙盒中編寫腳本
結構體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學
NSString 與 Unicode
代碼簽名探析
測試
架構
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅動開發(fā)
Collection View 動畫
截圖測試
MVVM 介紹
使 Mac 應用數(shù)據(jù)腳本化
一個完整的 Core Data 應用
插件
字符串
為 iOS 建立 Travis CI
先進的自動布局工具箱
動畫
為 iOS 7 重新設計 App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網絡應用實例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動畫解釋
響應式 Android 應用
初識 TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調試
項目介紹
Swift 的強大之處
測試并發(fā)程序
Android 通知中心
調試:案例學習
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學習的一代人
視頻
Playground 快速原型制作
Omni 內部
同步數(shù)據(jù)
設計優(yōu)雅的移動游戲
繪制像素到屏幕上
相機與照片
音頻 API 一覽
交互式動畫
常見的后臺實踐
糟糕的測試
避免濫用單例
數(shù)據(jù)模型和模型對象
Core Data
字符串本地化
View Controller 轉場
照片框架
響應式視圖
Square Register 中的擴張
DTrace
基礎集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設計的藝術
導航應用
線程安全類的設計
置換測試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機項目
Mach-O 可執(zhí)行文件
UI 測試
值對象
活動追蹤
依賴注入
Swift
項目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務
自定義 Collection View 布局
測試 View Controllers
訪談
收據(jù)驗證
數(shù)據(jù)同步
自定義 ViewController 容器轉場
游戲
調試核對清單
View Controller 容器
學無止境
XCTest 測試實戰(zhàn)
iOS 7
Layer 中自定義屬性的動畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲
代碼審查的藝術:Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴展
理解 Scroll Views
使用 VIPER 構建 iOS 應用
Android 中的 SQLite 數(shù)據(jù)庫支持
Fetch 請求
導入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機捕捉
語言標簽
同步案例學習
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識別
玩轉字符串
相機工作原理
Build 過程

理解 Scroll Views

可能你很難相信 UIScrollView 和一個標準的 UIView 差異并不大,scroll view 確實會多出一些方法,但這些方法只是和 UIView 的屬性很好的結合到一起了。因此,在要想弄懂 UIScrollView 是怎么工作之前,你需要先了解一下 UIView,特別是視圖渲染的兩步過程。

光柵化和組合

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

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

概念上,依次在每個視圖上放置獨立分層的圖片并最終產生一個圖片,單調的圖像更容易被理解,特別是如果你以前使用過像 Photoshop 這樣的工具。我們還有另外一篇文章詳細解釋了像素是如何繪制到屏幕上去的。

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

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

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

http://wiki.jikexueyuan.com/project/objc/images/3-3.png" alt="" />

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

http://wiki.jikexueyuan.com/project/objc/images/3-4.png" alt="" />

記住,我們才僅僅討論了一個視圖和它父視圖之間的組合操作。一旦這兩個視圖被組合到一起,組合的結果圖片將會和父視圖的父視圖進行組合,這是一個雪球效應。

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

CompositedPosition.x = View.frame.origin.x - Superview.bounds.origin.x;

CompositedPosition.y = View.frame.origin.y - Superview.bounds.origin.y;

正如之前所說的,如果一個視圖 bounds 的 origin 是 {0,0}。那么,我們得到這個公式:

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

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

我們可以通過幾個不同的 frames 看一下:

http://wiki.jikexueyuan.com/project/objc/images/3-5.png" alt="" />

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

Scroll View的Content Offset

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

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

CompositedPosition.x = View.frame.origin.x - Superview.bounds.origin.x;

CompositedPosition.y = View.frame.origin.y - Superview.bounds.origin.y;

我們減少 Superview.bounds.origin 的值(因為他們總是0)。但是如果他們不為0呢?我們用和前一個圖例相同的 frames,但是我們改變了紫色視圖 bounds 的 origin 為 {-30, -30}。得到下圖:

http://wiki.jikexueyuan.com/project/objc/images/3-6.png" alt="" />

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

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

注意前一個圖例,只要足夠的改變 bounds 的 origin,button 將會超出紫色視圖和 button 組合成的圖片的范圍。這也是當你足夠的移動 scroll view 時,一個視圖會消失!

世界之窗:Content Size

現(xiàn)在,最難的部分已經過去了,我們再看看 UIScrollView 另一個屬性:contentSize。 scroll view 的 content size 并不會改變其 bounds 的任何東西,所以這并不會影響 scroll view 如何組合自己的子視圖。反而,content size 定義了可滾動區(qū)域。scroll view 的默認 content size 為 {w:0, h:0}。既然沒有可滾動區(qū)域,用戶是不可以滾動的,但是 scroll view 仍然會顯示其 bounds 范圍內所有的子視圖。 當 content size 設置為比 bounds 大的時候,用戶就可以滾動視圖了。你可以認為 scroll view 的 bounds 為可滾動區(qū)域上的一個窗口:

http://wiki.jikexueyuan.com/project/objc/images/3-7.png" alt="" />

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

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

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

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

用Content Insets對窗口稍作調整

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

http://wiki.jikexueyuan.com/project/objc/images/3-8.png" alt="" />

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

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

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

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

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

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

http://wiki.jikexueyuan.com/project/objc/images/3-9.png" alt="" />

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

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

相關鏈接(強烈推薦):

計算機圖形渲染的流程

上一篇:線程安全類的設計下一篇:字符串