終于決定開始學(xué)習(xí)ios開發(fā)了,很久之前有過一次ios開發(fā)培訓(xùn),但是做完作業(yè)就落下了,一直沒有再撿起。想到如今ios開發(fā)如火如荼,再不學(xué)習(xí)真的要掉隊(duì)了,希望能督促自己在2個(gè)月內(nèi)完成ios開發(fā)的基礎(chǔ)學(xué)習(xí)以及寫出一個(gè)demo。這是第一篇,先總結(jié)下iphone的分辨率以及圖像基礎(chǔ)知識(shí),由于對(duì)圖形圖像的一些概念并不太熟,錯(cuò)誤在所難免,請(qǐng)大蝦指正。
1 Points/DPI & Pixels/PPI
在談到iPhone的分辨率之前,先要說(shuō)說(shuō)ios開發(fā)里面Points和Pixels的概念。在ios開發(fā)中,一個(gè)控件的坐標(biāo)尺寸都是用邏輯點(diǎn)Points來(lái)表示,Points是抽象單元,僅僅只是在坐標(biāo)空間起作用。我們實(shí)際看到的控件都是像素點(diǎn)Pixels。DPI(dot per inch)是每英寸(1英寸=2.54厘米)的邏輯點(diǎn)的個(gè)數(shù),比如iPhone5的邏輯分辨率320x568
,DPI為163。為PPI(pixel per inch)則是每英寸的像素點(diǎn)的個(gè)數(shù),比如iPhone5的物理分辨率為640x1136
,PPI為326,iPhone6 Plus的物理分辨率為1080 x 1092
,PPI為401。個(gè)人理解的一點(diǎn)是,顯示內(nèi)容多少跟DPI相關(guān),也就是我們?cè)诖a里面設(shè)定的大小,而顯示的精細(xì)程度則跟PPI和素材本身的分辨率相關(guān)。
Update: 另外要說(shuō)明的一點(diǎn)是,我們有時(shí)候看到一張圖片,大小為640X960,這個(gè)其實(shí)是水平和垂直方向的像素點(diǎn)數(shù)目,像素點(diǎn)多并不代表圖片就越大,還有個(gè)因素很重要,就是分辨率。對(duì)于兩張像素點(diǎn)一樣的圖片,比如都是640X960,其中一張圖片分辨率為144PPI,另一張圖片分辨率為72PPI,那么,分辨率為144的圖片會(huì)比72的小。
如下圖所示,ios應(yīng)用中,首先代碼中設(shè)定的Points首先會(huì)根據(jù)一定的scale factor
縮放因子渲染為像素。比如iPhone4之前的縮放因子都是1,而iPhone4-iPhone6因?yàn)樘岣吡朔直媛剩虼丝s放因子增加為2,而iPhone6 Plus分辨率更高,縮放因子為3。對(duì)于同樣大小的圖片,在不同的分辨率顯示會(huì)有差異,在高分辨率的機(jī)器上會(huì)變小,為了保證在所有型號(hào)機(jī)器上看起來(lái)圖片大小一致,所以需要不同大小的圖片,這也是ios開發(fā)中的圖片資源會(huì)有1x,2x以及3x這三種的原因。圖后附有xcode代碼打印出的Points和Pixels的值,可以驗(yàn)證一下。
####測(cè)試代碼
UIScreen *mainScreen = [UIScreen mainScreen];
NSLog(@"Screen bounds: %@, Screen resolution: %@, scale: %f, nativeScale: %f",
NSStringFromCGRect(mainScreen.bounds),mainScreen.coordinateSpace,mainScreen.scale,
mainScreen.nativeScale);
####輸出
iPhone4s:
Screen bounds: {{0, 0}, {320, 480}}, Screen resolution: <UIScreen: 0x7ba30360; bounds = {{0, 0}, {320, 480}}; mode = <UIScreenMode: 0x7ba30650; size = 640.000000 x 960.000000>>, scale: 2.000000, nativeScale: 2.000000
iPhone5:
Screen bounds: {{0, 0}, {320, 568}}, Screen resolution: <UIScreen: 0x7b932140; bounds = {{0, 0}, {320, 568}}; mode = <UIScreenMode: 0x7b9301b0; size = 640.000000 x 1136.000000>>, scale: 2.000000, nativeScale: 2.000000
iPhone6:
Screen bounds: {{0, 0}, {375, 667}}, Screen resolution: <UIScreen: 0x7fd340c0f6f0; bounds = {{0, 0}, {375, 667}}; mode = <UIScreenMode: 0x7fd340c0fba0; size = 750.000000 x 1334.000000>>, scale: 2.000000, nativeScale: 2.000000
iPhone6 Plus:
Screen bounds: {{0, 0}, {414, 736}}, Screen resolution: <UIScreen: 0x7f924b40b1d0; bounds = {{0, 0}, {414, 736}}; mode = <UIScreenMode: 0x7f924b40b6d0; size = 1242.000000 x 2208.000000>>, scale: 3.000000, nativeScale: 3.000000
除了iPhone6 Plus外,經(jīng)過第一步的根據(jù)scale factor進(jìn)行縮放后的像素就是最終顯示的物理像素。由于iPhone6 Plus的屏幕分辨率為1080 x 1920
, 而上一步縮放得到的分辨率為1242 × 2208
,因此需要再經(jīng)過一次向下取樣(downsample)的過程,對(duì)于一張圖片來(lái)說(shuō),大小變成了之前的87%(1080/1242=20/23)
左右,之前縮放的23個(gè)像素要映射到屏幕的20個(gè)像素上。我們?cè)陂_發(fā)的時(shí)候,3x的圖片的分辨率要調(diào)成1242 x 2208
,由ios去完成向下取樣的過程。示例參見http://www.paintcodeapp.com/news/iphone-6-screens-demystified
,而向下取樣帶來(lái)的影響參見這篇文章的分析http://oleb.net/blog/2014/11/iphone-6-plus-screen/。
2 再談iPhone6 Plus的分辨率設(shè)置
第一個(gè)疑問是為什么iPhone6 Plus的邏輯分辨率要用414*736呢,如果用360 x 640貌似也OK,放大三倍后正好是1080 x 1920,都可以省去后面那個(gè)downsample的步驟了。參考資料2詳細(xì)分析了iPhone6 Plus的邏輯分辨率這樣設(shè)置的原因,摘錄如下,先看圖:
- 如果邏輯分辨率用
360 x 640
,放大3x后確實(shí)正好跟屏幕分辨率一致,省去了doansample的步驟。但是這樣帶來(lái)的問題是6P的邏輯分辨率360x640
比 iPhone 6的375x667
還低,6P的大屏幕雖然很精細(xì),但是可顯示的實(shí)際內(nèi)容比6還少,太不科學(xué)。打個(gè)比方就是:相同字號(hào)的情況下,6如果一行顯示了25個(gè)字,而6P就會(huì)只能顯示24個(gè)字了。 - 如果邏輯分辨率為
540 x 960
,放大2x就可以跟屏幕分辨率一致,這樣也就不用3x的圖片了。但是這樣帶來(lái)的問題是6P的邏輯分辨率差不多是6的兩倍,確實(shí)可以顯示更多的內(nèi)容了,但是UI控件可以顯示的實(shí)際物理面積變小了,標(biāo)簽欄或?qū)Ш綑诎粹o的物理高度只有原來(lái)的81.5%(163/200),點(diǎn)擊面積只有原來(lái)的66.4%(81.5% x 81.5%),這樣點(diǎn)擊就更加困難了。 - 因此最好的方案應(yīng)該是圖中的iPhone6 Plus(a)了,物理像素
1242 x 2208
,在5.5英寸的屏幕上這個(gè)ppi就要達(dá)到461了,而蘋果最終并沒有采用這個(gè)方案,而是縮放到了1080 x 1920
。可能的原因是如果分辨率達(dá)到461ppi,則內(nèi)存消耗增大,電池消耗增大,工藝上可能也有難度。當(dāng)然如果后續(xù)技術(shù)提高后,克服了這些困難,則很可能不用downsample
了。 - 如果我們不用3x的圖片,而是將2x的圖片用在6P中,則會(huì)出現(xiàn)明顯的鋸齒或模糊。
3 ios開發(fā)中的坐標(biāo)系
ios開發(fā)中坐標(biāo)系至關(guān)重要,直接關(guān)系UI控件最終的位置關(guān)系。其中有三個(gè)屬性要重點(diǎn)關(guān)注:bounds,center和frame,斯坦福大學(xué)的ios開發(fā)教程中有很精辟的注解,下面是這幾個(gè)屬性的定義:
@property CGRect bounds; // your view’s internal drawing space’s origin and size
// The bounds property is what you use inside your view’s own implementation.
// It is up to your implementation as to how to interpret the meaning of bounds.origin.
@property CGPoint center; // the center of your view in your superview’s coordinate space
@property CGRect frame; // a rectangle in your superview’s coordinate space which entirely contains your view’s bounds.size
概括起來(lái)就是:
- 坐標(biāo)系從左上角開始。
- 坐標(biāo)系以Points為單元(注意,是邏輯點(diǎn)Points,不是像素Pixels)。最終ios會(huì)將UI控件自動(dòng)適配高分辨率,只要有相應(yīng)的2x和3x的素材提供。
- bounds指的是視圖自己的范圍區(qū)域。
- center指的是視圖的中心點(diǎn)在superview的坐標(biāo)系的坐標(biāo)。
- frame則是視圖的superview坐標(biāo)系中包裹了這個(gè)UIView的矩形區(qū)域。
- frame和bounds可能是相同的,但是不一定都相同,如果UIView本身旋轉(zhuǎn)了,則不會(huì)相同,具體見后面的圖示例。
下面這個(gè)圖詳細(xì)說(shuō)明了這幾個(gè)屬性的含義。
簡(jiǎn)單測(cè)試
測(cè)試代碼如下,在當(dāng)前UIView中加入一個(gè)UILabel,打印這個(gè)label的bounds,center以及frame,當(dāng)然這里沒有旋轉(zhuǎn)該UIView。可以看到label的center為{45, 35},正好是label在superview坐標(biāo)系中的坐標(biāo)。而label的bounds是針對(duì)自身那個(gè)矩形的,所以為{{0,0},{50,30}}。label的frame則是包裹它的矩形在superview坐標(biāo)系中的坐標(biāo),也就是{{20,20},{50, 30}}.
###測(cè)試代碼###
CGRect labelRect = CGRectMake(20, 20, 50, 30);
UILabel *label = [[UILabel alloc] initWithFrame:labelRect];
label.text = @"Hello!";
[self.view addSubview:label];
NSLog(@"center:%@, bounds:%@, frame:%@", NSStringFromCGPoint(self.view.center), NSStringFromCGRect(self.view.bounds),
NSStringFromCGRect(self.view.frame));
NSLog(@"center:%@, bounds:%@, frame:%@", NSStringFromCGPoint(label.center), NSStringFromCGRect(label.bounds),
NSStringFromCGRect(label.frame));
###輸出結(jié)果如下(模擬器為iPhone5s)###
2016-01-11 23:19:59.927 resolution[10719:2940751] center:{160, 284}, bounds:{{0, 0}, {320, 568}}, frame:{{0, 0}, {320, 568}}
2016-01-11 23:19:59.927 resolution[10719:2940751] center:{45, 35}, bounds:{{0, 0}, {50, 30}}, frame:{{20, 20}, {50, 30}}