轉(zhuǎn)載:http://www.cocoachina.com/swift/20161201/18198.html
前言
TangramKit是iOS系統(tǒng)下用Swift編寫的第三方界面布局框架。他集成了iOS的AutoLayout和SizeClass以及Android的五大容器布局體系以及HTML/CSS中的float和flex-box的布局功能和思想,目的是為iOS開(kāi)發(fā)人員提供一套功能強(qiáng)大封恰、多屏幕靈活適配、簡(jiǎn)單易用的UI布局解決方案举塔。Tangram的中文即七巧板的意思,取名的寓意表明這個(gè)布局庫(kù)可以非常靈巧和簡(jiǎn)單的解決各種復(fù)雜界面布局問(wèn)題求泰。他的同胞框架:MyLayout是一套用objective-C實(shí)現(xiàn)的界面布局框架央渣。二者的主體思想相同,實(shí)現(xiàn)原理則是通過(guò)擴(kuò)展UIView的屬性渴频,以及重載layoutSubviews方法來(lái)完成界面布局芽丹,只不過(guò)在一些語(yǔ)法和屬性設(shè)置上略有一些差異⊥鞯可以這么說(shuō)TangramKit是MyLayout布局庫(kù)的一個(gè)升級(jí)版本志衍。大家可以通過(guò)訪問(wèn)下面的github站點(diǎn)去下載最新的版本:
Swift版本TangramKit:https://github.com/youngsoft/TangramKit
OC版本MyLayout:https://github.com/youngsoft/MyLinearLayout
TangramKit演示效果圖
所見(jiàn)即所得和編碼之爭(zhēng)以及屏幕的適配
在我10多年的開(kāi)發(fā)生涯中暖庄,大部分時(shí)間都工作在客戶端上。從DOS到Windows再到UNIX再到2010年接觸iOS開(kāi)發(fā)這6年多的時(shí)間中楼肪,總感覺(jué)一無(wú)所獲培廓,原因呢是覺(jué)沒(méi)有什么積累。作為一個(gè)以編程為職業(yè)的人來(lái)說(shuō)如果不留下什么可以值得為大家所知的東西的話春叫,那將是一種職業(yè)上的遺憾肩钠。
就像每個(gè)領(lǐng)域都有工作細(xì)分一樣,現(xiàn)在的編程人員也有明確分工:有一部分人做的是后端開(kāi)發(fā)的工作暂殖,而有一部分人做的是前端開(kāi)發(fā)的工作价匠。二者相輔相成而完成了整個(gè)系統(tǒng)。后端開(kāi)發(fā)的重點(diǎn)在于實(shí)現(xiàn)高性能和高可用呛每,在數(shù)據(jù)處理上通常都是一個(gè)輸入一個(gè)加工然后一個(gè)輸出踩窖;而前端開(kāi)發(fā)的重點(diǎn)在于實(shí)現(xiàn)界面流暢性和美觀性,在數(shù)據(jù)處理上往往是多個(gè)輸入一個(gè)加工和多個(gè)輸出晨横。在技術(shù)層面上后端處理的對(duì)象是多線程多進(jìn)程以及數(shù)據(jù)洋腮,而前端處理的對(duì)象則是圖形繪制和以及界面布局和動(dòng)畫特效。
這篇文章的重點(diǎn)是介紹界面布局的核心手形,因此其他部分就不再展開(kāi)去說(shuō)了啥供。對(duì)于一個(gè)UI界面來(lái)說(shuō),好的界面布局體系往往能起到事半工倍的作用库糠。PC設(shè)備上因?yàn)槠聊豢偸菈虼蠡锖热鏥B,VF,PB,Dephi,AWT,Swing等語(yǔ)言或者環(huán)境下的應(yīng)用開(kāi)發(fā)非常方便,IDE環(huán)境中提供一個(gè)所見(jiàn)即所得的開(kāi)發(fā)面板(form)瞬欧,人們只要使用簡(jiǎn)單的拖拉拽動(dòng)作就可把各種界面元素加入到form中就可以形成一個(gè)小程序了贷屎。而開(kāi)發(fā)VC程序則相對(duì)麻煩,系統(tǒng)的IDE環(huán)境對(duì)可視化編程的支持沒(méi)有那么的完善黍判,因此大部分界面的構(gòu)建都需要通過(guò)編碼來(lái)完成豫尽。同時(shí)因?yàn)镻C設(shè)備屏幕較大而且標(biāo)準(zhǔn)統(tǒng)一,因此幾乎不存在界面要在各種屏幕尺寸適配的問(wèn)題顷帖。唯一引起爭(zhēng)議是可視化編程和純代碼編程的方式之爭(zhēng),這種爭(zhēng)議也體現(xiàn)在iOS應(yīng)用的開(kāi)發(fā)身上渤滞,那就是用XIB和SB以及純代碼編寫界面的好壞爭(zhēng)議贬墩。關(guān)于這個(gè)問(wèn)題個(gè)人的意見(jiàn)是各有各好:XIB/SB進(jìn)行布局時(shí)容易上手且所見(jiàn)即所得,但缺乏靈活性和可定制化妄呕;而純代碼則靈活性高可定制化強(qiáng)陶舞,缺點(diǎn)是不能所見(jiàn)即所得和代碼維護(hù)以及系統(tǒng)分層模糊。
再回到屏幕適配的話題來(lái)說(shuō)绪励,如果說(shuō)PC時(shí)代編程屏幕尺寸適配不是很重要的工作肿孵,那么到了移動(dòng)設(shè)備時(shí)代則不一樣了唠粥,適配往往成為整個(gè)工作的重點(diǎn)和難點(diǎn)。主要的原因是設(shè)備的屏幕尺寸和設(shè)備分辨率的多樣性的差異停做,而且要求在這么小的屏幕上布局眾多的要素晤愧,同時(shí)又要求界面美觀和友好的用戶體驗(yàn),這就非瞅入纾考驗(yàn)產(chǎn)品以及UI/UE人員和開(kāi)發(fā)人員的水平官份,同時(shí)這部分工作也占用了開(kāi)發(fā)者的大部分時(shí)間。在現(xiàn)有的兩個(gè)主流的移動(dòng)平臺(tái)上烙丛,Android系統(tǒng)因?yàn)楸旧碛布脚_(tái)差異性的原因舅巷,為了解決這些差異性而設(shè)計(jì)了一套非常方便的和友好的界面布局體系。它提出了布局容器的概念河咽,也就是有專門職責(zé)的布局容器視圖來(lái)管理和排列里面的子視圖钠右,根據(jù)實(shí)際中的應(yīng)用場(chǎng)景而把這些負(fù)責(zé)布局的容器視圖分類抽象出了線性布局、相對(duì)布局忘蟹、框架布局爬舰、表格布局、絕對(duì)布局這5大容器布局寒瓦,而這些也就構(gòu)成了Android系統(tǒng)布局體系的核心實(shí)現(xiàn)情屹。也正是這套布局機(jī)制使得Android系統(tǒng)能夠方便的勝任多種屏幕尺寸和分辨率在不同硬件設(shè)備上的UI界面展示。而對(duì)于iOS的開(kāi)發(fā)人員來(lái)說(shuō)杂腰,早期的設(shè)備只有單一的3.5in大小且分辨率也只有480x320和960x640這兩種類型的設(shè)備垃你,因此開(kāi)發(fā)人員只需要采用絕對(duì)定位的方式通過(guò)視圖的frame屬性設(shè)置來(lái)實(shí)現(xiàn)界面的布局,根本不需要考慮到屏幕的適配問(wèn)題喂很。但是這一切從蘋果后續(xù)依次發(fā)布iPhone4/5/6/7系列的設(shè)備后被打破了惜颇,整個(gè)iOS應(yīng)用的開(kāi)發(fā)也需要考慮到多屏幕尺寸和多分辨率的問(wèn)題了,這樣原始的frame方法進(jìn)行布局設(shè)置將不能滿足這些多屏幕的適配問(wèn)題了少辣,因此iOS提出了一套新的界面布局體系:AutoLayout以及SizeClass. ?這套機(jī)制通過(guò)設(shè)置視圖之間的位置和尺寸的約束以及對(duì)屏幕尺寸進(jìn)行分類的方式來(lái)完成界面的布局和屏幕的適配工作凌摄。
盡管如此, 雖然兩個(gè)移動(dòng)端平臺(tái)都提供了自己獨(dú)有且豐富的界面布局體系,但對(duì)于移動(dòng)客戶端開(kāi)發(fā)人員來(lái)說(shuō)界面布局和適配仍然是我們?cè)陂_(kāi)發(fā)中需要重點(diǎn)關(guān)注的因素之一漓帅。
布局的核心
我們知道锨亏,在界面開(kāi)發(fā)中我們直接操作的對(duì)象是視圖,視圖可以理解為一個(gè)具有特定功能的矩形區(qū)塊忙干,因此所謂的布局的本質(zhì)就是為視圖指定某個(gè)具體的尺寸以及指定其排列在屏幕上的位置器予。因此布局的動(dòng)作就分為兩個(gè)方面:一個(gè)是指定視圖的尺寸,一個(gè)是指定視圖的位置捐迫。
視圖的尺寸和位置
視圖的尺寸
視圖的尺寸就是指視圖矩形塊的大小乾翔,為了表征視圖的大小我們稱在屏幕水平方向的尺寸大小為寬度,而稱在屏幕垂直方向的尺寸大小為高度施戴,因此一個(gè)視圖的尺寸我們就可以用寬度和高度兩個(gè)維度的值來(lái)描述了反浓,寬度和高度的單位我們稱之為點(diǎn)萌丈。UIView中用bounds屬性的size部分來(lái)描述視圖的尺寸(bounds屬性的origin部分后面會(huì)介紹到)。 ?對(duì)于屏幕尺寸來(lái)說(shuō)同樣也用寬度和高度來(lái)描述雷则。在視圖層次體系結(jié)構(gòu)中的頂層視圖的尺寸和屏幕的尺寸是一致的辆雾,為了描述這個(gè)特殊的頂層視圖我們將這個(gè)頂層根視圖稱之為窗口,窗口的尺寸和屏幕的尺寸一樣大巧婶,同時(shí)窗口是一切視圖的容器視圖乾颁。一個(gè)視圖的尺寸我們可以用一個(gè)具體的數(shù)值來(lái)描述,比如某個(gè)視圖的寬度和高度分別為:100x200艺栈。我們稱這種定義的方式為絕對(duì)值類型的尺寸英岭。但是在實(shí)際中我們的一些視圖的尺寸并不能夠一開(kāi)始就被明確,原因是這些視圖的尺寸大小和其他視圖的尺寸大小有關(guān)湿右,也就是說(shuō)視圖的尺寸依賴于另外一個(gè)視圖或者另外一組視圖诅妹。比如說(shuō)有A和B兩個(gè)視圖,我們定義A視圖的寬度和B視圖的寬度相等毅人,而A視圖的高度則是B視圖高度的一半吭狡。也就是可以表述為如下:
A.bounds.size.width?=?B.bounds.size.width
A.bounds.size.height?=?B.bounds.size.height?/2
//父視圖S的高度等于里面子視圖A,B的高度的總和
S.bounds.size.height?=?A.bounds.size.height?+?B.bounds.size.height
我們稱為這種尺寸的定義方式為相對(duì)值類型的尺寸。在相對(duì)值類型的尺寸中, 視圖某個(gè)維度的尺寸所依賴的另外一個(gè)視圖可以是它的兄弟視圖丈莺,也可以是它的父視圖划煮,也可以是它的子視圖,甚至可以是它自身的其他維度缔俄。 這種視圖尺寸的依賴關(guān)系是可以傳遞和遞歸的弛秋,比如A依賴于B,而B(niǎo)右依賴于C俐载。 但是這種遞歸和傳遞關(guān)系不能形成一個(gè)閉環(huán)依賴蟹略,也就是說(shuō)在依賴關(guān)系的最終節(jié)點(diǎn)視圖的尺寸的值必須是一個(gè)絕對(duì)值類型或者特定的相對(duì)值類型(wrap包裹值),否則的話我們將形成約束沖突而進(jìn)入死循環(huán)的場(chǎng)景遏佣。
兩種尺寸約束依賴
視圖的尺寸之間的依賴關(guān)系還有兩種特定的場(chǎng)景:
某個(gè)視圖的尺寸依賴于里面所有子視圖的尺寸的大小或者依賴于視圖內(nèi)所展示的內(nèi)容的尺寸挖炬,我們稱這種依賴為包裹(wrap)。
某個(gè)視圖的尺寸依賴于所在父視圖的尺寸減去其他兄弟視圖所占用的尺寸的剩余尺寸也就是說(shuō)尺寸等于父視圖的尺寸和其兄弟視圖尺寸的差集状婶,我們稱這種依賴為填充(fill)意敛。
可以看出包裹和填充尺寸是相對(duì)值類型中的兩種特殊的類型,他所依賴的視圖并不是某個(gè)具體的視圖太抓,而是一些相關(guān)的視圖的集合空闲。
為了表征視圖的尺寸以及尺寸可以設(shè)置的值的類型,我們就需要對(duì)尺寸進(jìn)行建模走敌,在TangramKit框架中TGLayoutSize類就是一個(gè)尺寸類,這個(gè)類里面的equal方法則是用來(lái)設(shè)置視圖尺寸的各種類型的值:包括絕對(duì)值類型逗噩,相對(duì)值類型掉丽,以及包裹和填充的值類型等等跌榔。同時(shí)我們對(duì)UIView擴(kuò)展出了兩個(gè)屬性tg_width, ?tg_height分別用來(lái)表示視圖的布局寬度和布局高度。他其實(shí)是對(duì)原生的視圖bounds屬性中的size部分進(jìn)行了擴(kuò)充和延展捶障。原始的bounds屬性中的size部分只能設(shè)置絕對(duì)值類型的尺寸僧须,而不能設(shè)置相對(duì)值類型的尺寸。
視圖的位置
當(dāng)一個(gè)視圖的尺寸確定后项炼,接下來(lái)我們就需要確定視圖所在的位置了担平。所謂位置就是指視圖在屏幕中的坐標(biāo)位置,屏幕中的坐標(biāo)分為水平坐標(biāo)也就是x軸坐標(biāo)锭部,和垂直坐標(biāo)也就是y軸坐標(biāo)暂论。而這個(gè)坐標(biāo)原點(diǎn)在不同的系統(tǒng)中有區(qū)別:iOS系統(tǒng)采用左手坐標(biāo)系,原點(diǎn)都是在左上角拌禾,并且規(guī)定y軸在原點(diǎn)以下是正坐標(biāo)軸取胎,而原點(diǎn)以上是負(fù)坐標(biāo)軸,而x軸則在原點(diǎn)右邊是正坐標(biāo)軸湃窍,原點(diǎn)左邊是負(fù)坐標(biāo)軸闻蛀。OSX系統(tǒng)則采用右手坐標(biāo)系,原點(diǎn)在左下角您市,并且規(guī)定y軸在原點(diǎn)以上是正坐標(biāo)軸觉痛,而在原點(diǎn)以下是負(fù)坐標(biāo)軸,而x軸則在原點(diǎn)右邊是正坐標(biāo)軸茵休,原點(diǎn)左邊是負(fù)坐標(biāo)軸薪棒。
不同的坐標(biāo)系
因此視圖位置的確定我們需要考慮兩個(gè)方面的問(wèn)題:一個(gè)是位置是相對(duì)于哪個(gè)坐標(biāo)系?一個(gè)是視圖內(nèi)部的哪個(gè)部位來(lái)描述這個(gè)位置泽篮?
確定一個(gè)視圖的位置時(shí)總是應(yīng)該有一個(gè)參照物盗尸,在現(xiàn)有的布局體系中一般分為三種參照物:屏幕、父視圖帽撑、兄弟視圖泼各。
第一種以屏幕坐標(biāo)系作為參照來(lái)確定的位置稱為絕對(duì)位置,也就是以屏幕的左上角作為原點(diǎn)亏拉,每個(gè)視圖的位置都是距離屏幕左上角原點(diǎn)的一個(gè)偏移值扣蜻。這種絕對(duì)位置的設(shè)置方式的優(yōu)點(diǎn)是所有視圖的參照物都是一致的,便于比較和計(jì)算及塘,但缺點(diǎn)是對(duì)于那些多層次結(jié)構(gòu)的視圖以及帶滾動(dòng)效果的視圖來(lái)說(shuō)位置的確定則總是需要進(jìn)行動(dòng)態(tài)的變化和計(jì)算莽使。比如某個(gè)滾動(dòng)視圖內(nèi)的所有子視圖在滾動(dòng)時(shí)都需要重新去計(jì)算自己的位置。
第二種以父視圖坐標(biāo)系作為參照來(lái)確定的位置稱為相對(duì)位置笙僚,每個(gè)子視圖的位置都是距離父視圖左上角原點(diǎn)的一個(gè)偏移值芳肌。這樣的好處就是每個(gè)子視圖都不再需要關(guān)心屏幕的原點(diǎn),而只需要以自己的父視圖為原點(diǎn)進(jìn)行位置的計(jì)算就可以了,這種方式是目前大部分布局體系里面采用的定位方式亿笤,也是最方便的定位方式翎迁,缺點(diǎn)是不同層次之間的視圖的位置在進(jìn)行比較時(shí)需要一步步的往上進(jìn)行轉(zhuǎn)換,直到轉(zhuǎn)換到在窗口中的位置為止净薛。我們稱這種以父視圖坐標(biāo)系為原點(diǎn)進(jìn)行定位的位置稱為邊距汪榔,也就是離父視圖邊緣的距離。
第三種以兄弟視圖坐標(biāo)系作為參照來(lái)確定的位置稱為偏移位置肃拜,子視圖的位置是在關(guān)聯(lián)的兄弟視圖的位置的基礎(chǔ)之上的一個(gè)偏移值痴腌。比如A視圖在B視圖的右邊偏移5個(gè)點(diǎn),則表示為A視圖的左邊距離B視圖的右邊5個(gè)點(diǎn)的距離燃领。我們稱這種坐標(biāo)體系下的位置為間距士聪,也就是指定的是視圖之間的距離作為視圖的位置。采用間距的方式進(jìn)行定位只適合于同一個(gè)父視圖之間的兄弟視圖之間的定位方式柿菩。
各種坐標(biāo)系下的定位值
上面的三種定位方式各有優(yōu)缺點(diǎn)戚嗅,我們可以在實(shí)際中結(jié)合各種定位方式來(lái)完成視圖的位置設(shè)定。
上面我們介紹了定位時(shí)位置所基于的坐標(biāo)系枢舶,因?yàn)橐晥D并不是一個(gè)點(diǎn)而是一個(gè)矩形區(qū)塊懦胞,所以我們必須要明確的是視圖本身這個(gè)區(qū)塊的哪個(gè)點(diǎn)來(lái)進(jìn)行位置的設(shè)定。 在這里我們就要介紹視圖內(nèi)的坐標(biāo)系凉泄。我們知道視圖是一個(gè)矩形的區(qū)域躏尉,里面由無(wú)數(shù)個(gè)點(diǎn)構(gòu)成。假如我們以視圖左上角作為坐標(biāo)原點(diǎn)的話后众,那么視圖內(nèi)的任何一點(diǎn)都可以用水平方向的坐標(biāo)值和垂直方向的坐標(biāo)值來(lái)表示胀糜。對(duì)于水平方向的坐標(biāo)值來(lái)說(shuō)最左邊位置的點(diǎn)的坐標(biāo)值是0,最右邊位置的點(diǎn)的坐標(biāo)值是視圖的寬度蒂誉,中間位置的坐標(biāo)點(diǎn)的值是寬度的一半教藻,對(duì)于垂直方向的坐標(biāo)值來(lái)說(shuō)最上邊位置的點(diǎn)的坐標(biāo)值是0,最下邊位置的點(diǎn)的坐標(biāo)值是視圖的高度右锨,中間位置的坐標(biāo)點(diǎn)的值是高度的一半括堤。我們稱這幾個(gè)特殊的坐標(biāo)點(diǎn)為方位。因此一個(gè)視圖一共有9個(gè)方位點(diǎn)分別是:左上绍移、左中悄窃、左下、中上蹂窖、中中轧抗、中下、右上瞬测、右中横媚、右下纠炮。
視圖的九個(gè)方位
通過(guò)對(duì)方位點(diǎn)的定義,我們就不再需要去關(guān)心這些點(diǎn)的具體的坐標(biāo)值了分唾,因?yàn)樗枋隽艘晥D的某個(gè)特定的部位抗碰。而為了方便計(jì)算和處理狮斗,我們一般只需要指出視圖內(nèi)某個(gè)方位點(diǎn)在參照視圖的坐標(biāo)系里面的水平坐標(biāo)軸和垂直坐標(biāo)軸中的位置就可以完成視圖的位置定位了绽乔,因?yàn)橹灰_定了這個(gè)方位點(diǎn)的在參照視圖坐標(biāo)系里面的位置,就可以計(jì)算出這個(gè)視圖內(nèi)的任意的一個(gè)點(diǎn)在參照視圖坐標(biāo)軸里面的位置碳褒。所謂的位置定位就是把一個(gè)視圖內(nèi)坐標(biāo)系的某個(gè)點(diǎn)的坐標(biāo)值映射為參照視圖坐標(biāo)系里面的坐標(biāo)值的過(guò)程折砸。
視圖的坐標(biāo)轉(zhuǎn)換
iOS中UIView提供了一個(gè)屬性center,center屬性的意義就是定義視圖內(nèi)中心點(diǎn)這個(gè)方位在父視圖坐標(biāo)系中的坐標(biāo)值沙峻。我們?cè)賮?lái)考察一下UIView的bounds屬性睦授,上面的章節(jié)中我們有介紹bounds中的size部分用來(lái)描述一個(gè)視圖的尺寸,而origin部分又是用來(lái)描述什么呢摔寨? 我們知道在左手坐標(biāo)系里面去枷,一個(gè)視圖內(nèi)的左上角方位的坐標(biāo)值就是原點(diǎn)的坐標(biāo)值,默認(rèn)情況下原點(diǎn)的坐標(biāo)值是(0,0)是复。但是這個(gè)定義不是一成不變的删顶,也就是說(shuō)原點(diǎn)的坐標(biāo)值不一定是(0,0)。一個(gè)視圖bounds里面的origin部分所表達(dá)的意義就是視圖內(nèi)左上角的坐標(biāo)值淑廊,size部分所表達(dá)的意義就是視圖本身的尺寸逗余。這樣我們就可以通過(guò)下面的公式得出一個(gè)視圖內(nèi)9個(gè)方位(再次強(qiáng)調(diào)方位的概念是一個(gè)視圖內(nèi)的坐標(biāo)點(diǎn)的位置)的坐標(biāo)值:
左上方位?=?(A.bounds.origin.x,?A.bounds.origin.y)
左中方位?=?(A.bounds.origin.x,??A.bounds.origin.y?+?A.bounds.size.height?/?2)
左下方位?=?(A.bounds.origin.x,?A.bounds.origin.y?+?A.bounds.size.height)
中上方位?=?(A.bounds.origin.x?+?A.bounds.size.width/2,?A.bounds.origin.y)
中中方位?=?(A.bounds.origin.x?+?A.bounds.size.width/2,?A.bounds.origin.y?+?A.bounds.size.height/2)
中下方位?=?(A.bounds.origin.x?+?A.bounds.size.width/2,?A.bounds.origin.y?+?A.bounds.size.height)
右上方位?=?(A.bounds.origin.x?+?A.bounds.size.width,?A.bounds.origin.y)
右中方位?=?(A.bounds.origin.x?+?A.bounds.size.width,A.bounds.origin.y?+?A.bounds.size.height/2)
右下方位?=?(A.bounds.origin.x?+?A.bounds.size.width,A.bounds.origin.y?+?A.bounds.size.height)
對(duì)于位置定義來(lái)說(shuō)TangramKit中的TGLayoutPos類就是一個(gè)對(duì)位置進(jìn)行建模的類。TGLayoutPos類同時(shí)支持采用父視圖作為參考系和以兄弟視圖作為參考系的定位方式季惩,這可以通過(guò)為其中的equal方法設(shè)置不同類型的值來(lái)決定其定位方式录粱。為了實(shí)現(xiàn)視圖定位我們也為UIView擴(kuò)展出了3個(gè)水平方位的屬性:tg_left, tg_centerX,tg_right來(lái)表示左中右三個(gè)方位對(duì)象。3垂直方位的屬性:tg_top, tg_centerY,tg_bottom來(lái)表示上画拾、中啥繁、下三個(gè)方位。這6個(gè)方位對(duì)象將比原生的center屬性提供更加強(qiáng)大和豐富的位置定位能力青抛。
iOS系統(tǒng)的原生布局體系里面是通過(guò)bounds屬性和center屬性來(lái)進(jìn)行視圖的尺寸設(shè)置和位置設(shè)置的旗闽。bounds用來(lái)指定視圖內(nèi)的左上角方位的坐標(biāo)值,以及視圖的尺寸脂凶,而center則用來(lái)指定視圖的中心點(diǎn)方位在父視圖這個(gè)坐標(biāo)體系里面的坐標(biāo)值宪睹。為了簡(jiǎn)化設(shè)置UIView提供了一個(gè)簡(jiǎn)易的屬性frame可以用來(lái)直接設(shè)置一個(gè)視圖的尺寸和位置,frame中的origin部分指定視圖左上角方位在父視圖坐標(biāo)系里面的坐標(biāo)值蚕钦,而size部分則指定了視圖本身的尺寸亭病。frame屬性并不是一個(gè)實(shí)體屬性而是一個(gè)計(jì)算類型的屬性,在我們沒(méi)有對(duì)視圖進(jìn)行坐標(biāo)變換時(shí)(視圖的transform未設(shè)置時(shí))我們可以得到如下的frame屬性的偽代碼實(shí)現(xiàn):
public?var?frame:CGRect
{
get?{
let?x?=?self.center.x??-?self.bounds.size.width?/?2
let?y?=?self.center.y??-?self.bounds.size.height?/?2
let?width?=?self.bounds.size.width
let?height?=?self.bounds.size.height
return?CGRect(x:x,?y:y,?width:width,?height:height)
}
set?{
self.center?=?CGPoint(x:newValue.origin.x??+??newValue.size.width?/?2,?y:?newValue.origin.y?+??newValue.size.height?/?2)
self.bounds.size??=?newValue.size
}
}
綜上所述嘶居,我們可以看出罪帖,所謂視圖布局的核心促煮,就是確定一個(gè)視圖的尺寸,和確定視圖在參考視圖坐標(biāo)系里面的坐標(biāo)位置整袁。為了靈活處理和計(jì)算菠齿,視圖的尺寸可以設(shè)置為絕對(duì)值類型,也可以設(shè)置為相對(duì)值類型坐昙,也可以設(shè)置為特殊的包裹或者填充值類型绳匀;視圖的位置則可以指定視圖中的任意的方位,以及設(shè)置這個(gè)方位的點(diǎn)在窗口坐標(biāo)系或者父視圖坐標(biāo)系或者兄弟坐標(biāo)系中的坐標(biāo)值炸客。正是提供的這些多樣的設(shè)置方式疾棵,我們就可以在不同的場(chǎng)景中使用不同的設(shè)置來(lái)完成各種復(fù)雜界面的布局。
TangramKit布局框架
在您不了解TangramKit之前痹仙,可以先通過(guò)下面一個(gè)例子來(lái)感受和體驗(yàn)一下TangramKit的布局構(gòu)建語(yǔ)法:
有一個(gè)容器視圖S的寬度是100而高度則等于由四個(gè)從上到下依次排列的子視圖A,B,C,D的高度總和是尔。
子視圖A的左邊距占用父視圖寬度的20%,而右邊距則占用父視圖寬度的30%开仰,高度則等于自身的寬度拟枚。
子視圖B的左邊距是40,寬度則占用父視圖的剩余寬度众弓,高度是40恩溅。
子視圖C的寬度占用父視圖的所有寬度,高度是40田轧。
子視圖D的右邊距是20暴匠,寬度是父視圖寬度的50%,高度是40傻粘。
演示效果圖
代碼實(shí)現(xiàn)如下:
let?S?=?TGLinearLayout(.vert)
S.tg_vspace?=?10
S.tg_width.equal(100)
S.tg_height.equal(.wrap)
let?A?=?UIView()
A.tg_left.equal(20%)
A.tg_right.equal(30%)
A.tg_height.equal(A.tg_width)
S.addSubview(A)
let?B?=?UIView()
B.tg_left.equal(40)
B.tg_width.equal(.fill)
B.tg_height.equal(40)
S.addSubview(B)
let?C?=?UIView()
C.tg_width.equal(.fill)
C.tg_height.equal(40)
S.addSubview(C)
let?D?=?UIView()
D.tg_right.equal(20)
D.tg_width.equal(50%)
D.tg_height.equal(40)
S.addSubview(D)
因?yàn)門angramKit對(duì)布局位置類和布局尺寸類的方法重載了運(yùn)算符:~=每窖、>=、<=弦悉、+=窒典、-=、*=稽莉、/=所以您可以用更加簡(jiǎn)潔的代碼進(jìn)行編寫:
let?S?=?TGLinearLayout(.vert)
S.tg_vspace?=?10
S.tg_width?~=100
S.tg_height?~=.wrap
let?A?=?UIView()
A.tg_left?~=20%
A.tg_right?~=30%
A.tg_height?~=A.tg_width
S.addSubview(A)
let?B?=?UIView()
B.tg_left?~=40
B.tg_width?~=.fill
B.tg_height?~=40
S.addSubview(B)
let?C?=?UIView()
C.tg_width?~=.fill
C.tg_height?~=40
S.addSubview(C)
let?D?=?UIView()
D.tg_right?~=20
D.tg_width?~=50%
D.tg_height?~=40
S.addSubview(D)
通過(guò)上面的代碼瀑志,您可以看出用TangramKit實(shí)現(xiàn)的布局代碼和上面場(chǎng)景描述文本幾乎相同,非常的利于閱讀和理解污秆。那么這些系統(tǒng)又是如何實(shí)現(xiàn)的呢劈猪?
實(shí)現(xiàn)原理
我們知道在對(duì)任何一個(gè)視圖進(jìn)行布局時(shí),最終都是通過(guò)設(shè)置視圖的尺寸和視圖的位置來(lái)完成的良拼。在iOS中我們可以通過(guò)UIView的bounds屬性來(lái)完成視圖的尺寸設(shè)置战得,而通過(guò)center屬性來(lái)完成視圖的位置設(shè)置。為了進(jìn)行簡(jiǎn)單的操作庸推,系統(tǒng)提供了frame這個(gè)屬性來(lái)簡(jiǎn)化對(duì)尺寸和位置的設(shè)置常侦。這個(gè)過(guò)程不管是原始的方法還是后續(xù)的AutoLayout其實(shí)現(xiàn)的最終機(jī)制都是一致的浇冰。每當(dāng)一個(gè)視圖的尺寸改變或者要求重新布局時(shí),系統(tǒng)都會(huì)調(diào)用視圖的方法:
open?func?layoutSubviews()
而我們可以在UIView的派生類中重載上面的方法來(lái)實(shí)現(xiàn)對(duì)這個(gè)視圖里面的所有子視圖的重新布局聋亡,至于如何布局子視圖則是需要根據(jù)應(yīng)用場(chǎng)景而定肘习。在編程時(shí)我們經(jīng)常會(huì)用到一些視圖,這種視圖只是負(fù)責(zé)將里面的子視圖按照某種規(guī)則進(jìn)行排列和布局坡倔,而別無(wú)其他的作用漂佩。因此我們稱這種視圖為容器視圖或者稱為布局視圖。TangramKit框架對(duì)種視圖進(jìn)行了建模而提供了一個(gè)從UIView派生的布局視圖基類TGBaseLayout致讥。這個(gè)類的作用就是專門負(fù)責(zé)對(duì)加入到其中的所有子視圖進(jìn)行布局排列仅仆,它是通過(guò)重載layoutSubviews方法來(lái)完成這個(gè)工作的。剛才我們說(shuō)過(guò)如何排列容器視圖中的子視圖是要根據(jù)具體的應(yīng)用場(chǎng)景而定垢袱, 比如有可能是所有子視圖從上往下按照添加的順序依次排列,或者子視圖按照某種約束依賴關(guān)系來(lái)進(jìn)行布局排列港柜,或者子視圖需要多行多列的排列等等请契。因此我們對(duì)常見(jiàn)的布局應(yīng)用場(chǎng)景進(jìn)行了抽象,通過(guò)建立不同的TGBaseLayout的派生類來(lái)實(shí)現(xiàn)不同的布局處理:
線性布局TGLinearLayout:線性布局里面的所有子視圖都按照添加的順序依次從上到下或者依次從左到右進(jìn)行排列夏醉。根據(jù)排列的方向可以分為垂直線性布局和水平線性布局爽锥。線性布局和iOS9上的UIStackView以及Android中的線性布局LinearLayout提供一樣的功能。
框架布局TGFrameLayout: 框架布局里面的所有子視圖布局時(shí)和添加的順序無(wú)關(guān)畔柔,而是按照設(shè)定的位置吐纫模靠在布局視圖的:左上、左中靶擦、左下腮考、中上、中中玄捕、中下踩蔚、右上、右中枚粘、右下馅闽、填充這個(gè)10個(gè)方位中的任何一個(gè)位置上♀善框架布局里面的子視圖只跟框架布局視圖的邊界建立約束關(guān)系福也。框架布局和Android中的框架布局FrameLayout提供一樣的功能攀圈。
表格布局TGTableLayout:表格布局里面的子視圖可以進(jìn)行多行多列的排列暴凑。在使用時(shí)要先添加行,然后再在行里面添加列量承,每行的列數(shù)可以隨意確定肘交。因?yàn)楸砀癫季质蔷€性布局TGLinearLayout的派生類,所以表格布局也分為垂直表格布局和水平表格布局笨篷。垂直表格布局中的行是從上到下中捆,而列則是從左到右排列;水平表格布局中的行是從左到右席舍,而列是從上到下排列的。表格布局和Android中的表格布局TableLayout以及HTML中的table,tr,td元素提供一樣的功能。
相對(duì)布局TGRelativeLayout: ?相對(duì)布局里面的子視圖和添加的順序無(wú)關(guān)球凰,而是按照子視圖之間設(shè)定的尺寸約束依賴和位置約束依賴進(jìn)行布局排列。因此相對(duì)布局里面的所有子視圖都要設(shè)置位置和尺寸的約束和依賴關(guān)系腿宰。相對(duì)布局和iOS的AutoLayout以及Android中的相對(duì)布局RelativeLayout提供一樣的功能呕诉。
流式布局TGFlowLayout: 流式布局里面的子視圖按照添加的順序依次從某個(gè)方向排列,而當(dāng)遇到了這個(gè)方向上的排列數(shù)量限制或者容器的尺寸限制后將會(huì)另起一行吃度,而重新按照原先的方向依次排列甩挫。最終這個(gè)布局中的子視圖將形成多行多列的排列展示。流式布局和線性布局的區(qū)別是椿每,線性布局只是單行或者單列的伊者,而流式布局則是多行多列。流式布局和表格布局的區(qū)別是间护,表格布局有明確行的概念亦渗,在使用前要添加行再添加列,而流式布局則沒(méi)有明確行的概念汁尺,由布局自動(dòng)生成行和列法精。根據(jù)排列的方向和限制的規(guī)則,流式布局分為垂直數(shù)量約束布局痴突、垂直內(nèi)容約束布局搂蜓、水平數(shù)量約束布局、水平內(nèi)容約束布局四種布局苞也。流式布局實(shí)現(xiàn)了HTML/CSS3中的flex-box的子集的功能洛勉。
浮動(dòng)布局TGFloatLayout:浮動(dòng)布局里面的子視圖按照添加的順序,并且按照每個(gè)子視圖自身設(shè)定的浮動(dòng)規(guī)則向某個(gè)方向進(jìn)行浮動(dòng)腿绯伲靠收毫。當(dāng)子視圖的尺寸無(wú)法容納到布局視圖的剩余空間時(shí),則會(huì)自動(dòng)尋找一個(gè)能夠容納自身尺寸的最佳位置進(jìn)行浮動(dòng)鸵罂保靠此再。浮動(dòng)布局里面的子視圖并不是有規(guī)則的多行多列的排列。根據(jù)子視圖可以浮動(dòng)的方向浮動(dòng)布局分為垂直浮動(dòng)布局和水平浮動(dòng)布局玲销。浮動(dòng)布局和HTML/CSS中的float定位實(shí)現(xiàn)了相同的功能输拇。
路徑布局TGPathLayout: 路徑布局里面的子視圖按照一個(gè)提供的數(shù)學(xué)函數(shù)得到的曲線路徑等距離的根據(jù)添加的順序依次排列。所有的子視圖的位置都是根據(jù)函數(shù)曲線中距離相等的點(diǎn)而確定的贤斜。路徑布局提供了直角坐標(biāo)系策吠、參數(shù)方式逛裤、極坐標(biāo)系三種曲線的構(gòu)建方法。路徑布局是TangramKit中的獨(dú)有的一種布局猴抹。
上述的7個(gè)派生類分別的實(shí)現(xiàn)了大部分的不同的應(yīng)用場(chǎng)景带族。在每個(gè)派生類的layoutSubviews的實(shí)現(xiàn)中都按照描述的規(guī)則來(lái)設(shè)置子視圖的尺寸bounds和位置center屬性。也就是說(shuō)最終的子視圖的尺寸和位置是在布局視圖中的layoutSubviews中進(jìn)行設(shè)置的蟀给。那么我們就必須要提供另外一套子視圖的布局尺寸和布局位置的設(shè)置方法蝙砌,以便在布局視圖布局時(shí)將子視圖設(shè)置好的布局尺寸和布局位置轉(zhuǎn)化為真實(shí)的視圖尺寸和視圖位置。為此TangramKit專門提供了一個(gè)視圖的布局尺寸類TGLayoutSize用來(lái)進(jìn)行子視圖的布局尺寸的設(shè)置跋理,一個(gè)視圖的布局位置類TGLayoutPos用來(lái)進(jìn)行子視圖的布局位置的設(shè)置择克。我們對(duì)UIView建立了一個(gè)extension。分別擴(kuò)展出了2個(gè)布局尺寸對(duì)象和6個(gè)布局位置對(duì)象:
extension?UIView
{
//左邊位置
var?tg_left:TGLayoutPos{get}
//上邊位置
var?tg_top:TGLayoutPos{get}
//右邊位置
var?tg_right:TGLayoutPos{get}
//下邊位置
var?tg_bottom:TGLayoutPos{get}
//水平中心點(diǎn)位置
var?tg_centerX:TGLayoutPos{get}
//垂直中心點(diǎn)位置
var?tg_centerY:TGLayoutPos{get}
//寬度尺寸
var?tg_width:TGLayoutSize{get}
//高度尺寸
var?tg_height:TGLayoutSize{get}
}
也就是說(shuō)我們將不再直接設(shè)置子視圖的bounds和center(這兩個(gè)屬性只會(huì)在布局視圖中的layoutSubviews中設(shè)置)屬性了前普,而是直接操作UIView擴(kuò)展出來(lái)的布局位置對(duì)象和布局尺寸對(duì)象肚邢。如果把布局視圖的layoutSubviews比作一個(gè)數(shù)學(xué)函數(shù)的話,那么我們就能得到如下的方程式:
UIView.center?=?TGXXXLayout.layoutSubviews(UIView.tg_left,?UIView.tg_top,?UIView.tg_right,?UIView.tg_bottom,UIView.tg_centerX,UIView.tg_centerY)
UIView.bounds?=?TGXXXLayout.layoutSubviews(UIView.tg_width,?UIView.tg_height)
因此我們可以看出不同的TGBaseLayout的派生類因?yàn)槔锩娴牟季址椒ú幌嗤鴮?dǎo)致子視圖的位置和尺寸的計(jì)算方法不同道偷,從而得到了我們想要的效果。那么為什么要用6個(gè)布局位置對(duì)象和2個(gè)布局尺寸對(duì)象來(lái)設(shè)置子視圖的位置和尺寸而不直接用bounds和center呢记劈? 原因在于bounds和center只提供了有限的設(shè)置方法而布局位置對(duì)象和布局尺寸對(duì)象則提供了功能更加強(qiáng)大的設(shè)置方法,而這些方法又可以簡(jiǎn)化我們的編程并巍,以及可以很方便的適配各種不同尺寸的屏幕目木。(還記得我們上面的例子里面,尺寸和位置可以設(shè)置為數(shù)值,.wrap, .fill,以及百分比的值嗎懊渡?)刽射。
TangramKit為了存儲(chǔ)這些擴(kuò)展的布局位置和布局尺寸對(duì)象,內(nèi)部是使用了objc的runtime機(jī)制提供的動(dòng)態(tài)屬性創(chuàng)建的方法:
public?func?objc_getAssociatedObject(_?object:?Any!,?_?key:?UnsafeRawPointer!)?->?Any!
系統(tǒng)通過(guò)這個(gè)方法來(lái)關(guān)聯(lián)視圖對(duì)象的那6個(gè)布局位置和2個(gè)布局尺寸對(duì)象剃执。
上面的代碼中我們看到了布局容器視圖通過(guò)layoutSubviews方法來(lái)實(shí)現(xiàn)對(duì)子視圖的重新布局誓禁。而且也提到了當(dāng)容器視圖的尺寸發(fā)生變化時(shí)也會(huì)激發(fā)對(duì)layoutSubviews的調(diào)用。除了自動(dòng)激發(fā)外肾档,我們可以通過(guò)手動(dòng)調(diào)用布局視圖的setNeedLayout方法來(lái)實(shí)現(xiàn)布局視圖的layoutSubviews調(diào)用摹恰。當(dāng)我們?cè)谠O(shè)置子視圖的布局位置和布局尺寸時(shí),系統(tǒng)內(nèi)部會(huì)在設(shè)置完成后調(diào)用布局視圖的setNeedLayout的方法怒见,因此只要對(duì)子視圖的布局位置和布局尺寸進(jìn)行設(shè)置都會(huì)重新激發(fā)布局視圖的布局視圖俗慈。那么對(duì)子視圖的frame,bounds,center真實(shí)位置和尺寸的改變呢?我們也要激發(fā)布局視圖的重新布局遣耍。為了解決這個(gè)問(wèn)題闺阱,我們引入了KVO的機(jī)制。布局視圖在添加子視圖時(shí)會(huì)監(jiān)聽(tīng)加入到其中的子視圖的frame,bounds,center的變化舵变,并在其變化時(shí)調(diào)用布局視圖的setNeedLayout來(lái)激發(fā)布局視圖的重新布局酣溃。我們知道每次當(dāng)一個(gè)視圖調(diào)用addSubview添加子視圖時(shí)都會(huì)激發(fā)調(diào)用者的方法:didAddSubview瘦穆。為了實(shí)現(xiàn)對(duì)子視圖的變化的監(jiān)控,布局視圖重載了這個(gè)方法并對(duì)子視圖的isHidden,frame,center進(jìn)行監(jiān)控:
override?open?func?didAddSubview(_?subview:?UIView)?{
super.didAddSubview(subview)
subview.addObserver(self,?forKeyPath:"isHidden",?options:?NSKeyValueObservingOptions.new,?context:?nil)
subview.addObserver(self,?forKeyPath:"frame",?options:?NSKeyValueObservingOptions.new,?context:?nil)
subview.addObserver(self,?forKeyPath:"center",?options:?NSKeyValueObservingOptions.new,?context:?nil)
}
override?open?func?willRemoveSubview(_?subview:?UIView)?{
super.willRemoveSubview(subview)
subview.removeObserver(self,?forKeyPath:?"isHidden")
subview.removeObserver(self,?forKeyPath:?"frame")
subview.removeObserver(self,?forKeyPath:?"center")
}
當(dāng)子視圖的frame或者center變更時(shí)赊豌,將會(huì)激發(fā)布局視圖的重新布局扛或。上面曾經(jīng)說(shuō)過(guò),在布局視圖重新布局子視圖時(shí)最終會(huì)調(diào)整子視圖的bounds和center.那么這樣就有可能會(huì)形成循環(huán)的重新布局亿絮,為了解決這種循環(huán)遞歸的情況告喊,布局視圖在layoutSubviews調(diào)用進(jìn)行布局前設(shè)置了一個(gè)布局中的標(biāo)志,而在所有子視圖布局完成后將恢復(fù)這個(gè)布局中的標(biāo)志派昧。因此當(dāng)我們布局視圖通過(guò)KVO監(jiān)控到子視圖的位置和尺寸變化時(shí)黔姜,則會(huì)判斷那個(gè)布局中的標(biāo)志,如果當(dāng)前是在布局中則不會(huì)再次激發(fā)布局視圖的重新布局蒂萎,從而防止了死循環(huán)的發(fā)生秆吵。
這就是TangramKit布局實(shí)現(xiàn)的原理,下面的圖表列出了TangramKit的整個(gè)布局框架的類體系結(jié)構(gòu):
TangramKit布局框架體系架構(gòu)
布局位置類和布局尺寸類
在前面的介紹布局核心的章節(jié)以及布局實(shí)現(xiàn)原理的章節(jié)里面我們有說(shuō)道布局位置類和布局尺寸類五慈。之所以系統(tǒng)不直接操作視圖的bounds和center屬性而是通過(guò)擴(kuò)展視圖的2個(gè)布局尺寸屬性和6個(gè)布局位置屬性來(lái)進(jìn)行子視圖的布局設(shè)置纳寂。原因是后者能夠提供豐富和多樣的設(shè)置。而且我們?cè)诰幊虝r(shí)也不再需要通過(guò)設(shè)置視圖的frame來(lái)實(shí)現(xiàn)布局了泻拦,即使設(shè)置也可能會(huì)失效毙芜。
比重類TGWeight
TGWeight類的值表示尺寸或者位置的大小是父布局視圖的尺寸或者剩余空間的尺寸的比例值,也就是說(shuō)值的大小依賴于父布局視圖的尺寸或者剩余空間的尺寸的大小而確定争拐,這樣子視圖就不需要明確的指定位置和尺寸的大小了腋粥,非常適合那些需要適配屏幕的尺寸和位置的場(chǎng)景。 至于是父視圖的尺寸還是父視圖剩余空間的尺寸則要根據(jù)其所在的布局視圖的上下文而確定架曹。比如:
//假如A,b是在一個(gè)垂直線性布局下的子視圖
A.tg_width.equal(TGWeight(20))???//A的寬度是父布局視圖寬度的20%
A.tg_height.equal(TGWeight(30))??//A的高度是父布局視圖剩余高度的30%
B.tg_left.equal(TGWeight(40))??//B的左邊距是父視圖寬度的40%
B.tg_top.equal(TGWeight(10))??//B的頂部間距時(shí)父視圖的剩余高度的10%
為了簡(jiǎn)化和更加直觀的表示比重類型的值隘冲,我們重載%運(yùn)算符,這樣上面的代碼就可以簡(jiǎn)寫為如下更加直觀的方式:
//假如A是在一個(gè)垂直線性布局下的子視圖
A.tg_width.equal(20%)???//A的寬度是父布局視圖寬度的20%
A.tg_height.equal(30%)??//A的高度是父布局視圖剩余高度的30%
B.tg_left.equal(40%)??//B的左邊距是父視圖寬度的40%
B.tg_top.equal(10%)??//B的頂部間距時(shí)父視圖的剩余高度的10%
下面的列表中列出了在各種布局下視圖的尺寸和位置的TGWeight類型值所代表的意義:
為了表示方便绑雄,我們把:
線性布局簡(jiǎn)稱L
垂直線性布局簡(jiǎn)稱為L(zhǎng)V
水平線性布局簡(jiǎn)稱為L(zhǎng)H
框架布局簡(jiǎn)稱為FR
垂直表格布局簡(jiǎn)稱為TV
水平表格布局簡(jiǎn)稱為TH
相對(duì)布局簡(jiǎn)稱為R
浮動(dòng)布局簡(jiǎn)稱FO
流式布局FL
路徑布局簡(jiǎn)稱P
布局視圖的非布局父視圖S
所有布局簡(jiǎn)稱ALL
位置尺寸類型父視圖尺寸父視圖剩余空間尺寸
tg_leftLV/FR/S/THLH/TV
tg_topLH/FR/S/TVLV/TH
tg_rightLV/FR/S/THLH/TV
tg_bottomLH/FR/S/TVLV/TH
tg_centerXLV/FR/TH-
tg_centerYLH/FR/TV-
tg_widthLV/FR/S/R/TH/PLH/TV/FO/FL
tg_heightLH/FR/S/R/TV/PLV/TH/FO/FL
布局尺寸類TGLayoutSize
布局尺寸類用來(lái)描述視圖布局核心中的視圖尺寸展辞。我們對(duì)UIView擴(kuò)展出了2個(gè)布局尺寸對(duì)象 :
public?var?tg_width:TGLayoutSize
public?var?tg_height:TGLayoutSize
分別用來(lái)實(shí)現(xiàn)視圖的寬度和高度的布局尺寸設(shè)置。在TGLayoutSize類中万牺,我們可以通過(guò)方法equal來(lái)設(shè)置視圖尺寸的多種類型的值罗珍,類中是通過(guò)重載equal方法來(lái)實(shí)現(xiàn)多種類型的值的設(shè)置的。
public?func?equal(_?size:CGFloat,?increment:CGFloat?=?0,?multiple:CGFloat?=?1)?->TGLayoutSize
public?func?equal(_?weight:TGWeight,?increment:CGFloat?=?0,?multiple:CGFloat?=?1)?->TGLayoutSize
public?func?equal(_?array:[TGLayoutSize],?increment:CGFloat?=?0,?multiple:CGFloat?=?1)?->TGLayoutSize
public?func?equal(_?view:UIView,increment:CGFloat?=?0,?multiple:CGFloat?=?1)?->TGLayoutSize
public?func?equal(_?dime:TGLayoutSize!,?increment:CGFloat?=?0,?multiple:CGFloat?=?1)?->TGLayoutSize
上面的方法中我們可以通過(guò)equal方法來(lái)設(shè)置:
CGFloat類型的值表示視圖的尺寸是一個(gè)絕對(duì)值類型的尺寸值杏愤。比如:
A.tg_width.equal(100)??//A的寬度為100
A.tg_height.equal(200)?//A的高度為200
TGWeight類型的值表示視圖的尺寸是一個(gè)依賴于父視圖尺寸的相對(duì)比例值靡砌。(具體見(jiàn)上面TGWeight類型值的定義和使用)
//假如A是在一個(gè)垂直線性布局下的子視圖
A.tg_width.equal(20%)???//A的寬度是父布局視圖寬度的20%
A.tg_height.equal(30%)??//A的高度是父布局視圖剩余高度的30%
TGLayoutSize類型的值表示視圖的尺寸和另外一個(gè)尺寸對(duì)象的值相等,這也是一種相對(duì)值類型的尺寸值珊楼,通過(guò)設(shè)置這種尺寸的依賴我們就可以不必要明確的指定一個(gè)具體的值通殃,而是會(huì)隨著所以依賴的尺寸變化而變化。設(shè)置為TGLayoutSize類型的值通常用于在相對(duì)布局中的子視圖,當(dāng)然也可以在其他類型的布局中使用画舌。下面是一個(gè)展示的例子:
A.tg_width.equal(B.tg_width)??//A的寬度等于B的寬度
A.tg_height.equal(A.tg_width)??//A的高度等于A的寬度
UIView類型的值其實(shí)就是TGLayoutSize的簡(jiǎn)化版本設(shè)置堕担,表示某個(gè)維度的尺寸值等于指定視圖的相同維度的尺寸值。比如:
A.tg_width.equal(B)???//表示A視圖的寬度等于B視圖的寬度
A.tg_height.equal(A.superview)??//表示A視圖的高度等于父視圖的高度曲聂。
[TGLayoutSize]數(shù)組類型的值霹购,只用在相對(duì)布局里面的子視圖設(shè)置才有意義,其他的類型的布局中設(shè)置這種類型的值無(wú)效朋腋。他表示子視圖的尺寸和數(shù)組里面的所有子視圖來(lái)等分父布局視圖的尺寸齐疙。比如:
//A,B,C,D都是相對(duì)布局視圖里面的子視圖,我們希望A,B,C,D這四個(gè)子視圖來(lái)均分父視圖的寬度,這樣A,B,C,D都不需要明確的指定寬度了旭咽。
A.tg_width.equal([B.tg_width,?C.tg_width,?D.tg_width])
A.tg_width.equal(B.tg_width)??//A和B的寬度相等
A.tg_width.equal([B.tg_width])?//A和B的寬度相等并且平分布局視圖的寬度贞奋,也就是A,B的寬度都是布局視圖的寬度的一半
特殊類型的值。為了簡(jiǎn)化尺寸的設(shè)置我們定義了三種特殊類型的尺寸值:
下面是這三個(gè)特殊值使用的例子:
A.tg_width.equal(.wrap)??//A視圖的寬度由里面的所有子視圖或者內(nèi)容包裹而確定穷绵。
A.tg_height.equal(.fill)???//A視圖的高度填充滿父視圖的剩余高度空間轿塔。
B.tg_width.equal(.average)??//B視圖的寬度將會(huì)和其他兄弟視圖均分父視圖的寬度。
fill: 他表示視圖的尺寸的值將會(huì)填充滿父視圖的剩余空間仲墨,也就是說(shuō)視圖的尺寸值是依賴于父視圖的尺寸的大小勾缭。
average:他表示視圖的尺寸將和其兄弟視圖一起來(lái)均分父視圖的尺寸,這樣所有兄弟視圖的尺寸都將相等目养。
wrap: 他表示尺寸的值由布局視圖的所有子視圖的尺寸或者由子視圖的內(nèi)容包裹而成。也就是尺寸的大小是由子視圖或者視圖的內(nèi)容共同決定的癌蚁,這樣視圖的尺寸將依賴其內(nèi)部的子視圖的尺寸或者子視圖內(nèi)容的大小。
上面列出了布局尺寸類中的equal方法可以設(shè)置的值的類型洽洁,我們還看到了方法中存在著另外兩個(gè)默認(rèn)的參數(shù):increment 和multiple這兩個(gè)參數(shù)的意義表示在尺寸等于上述類型的值的基礎(chǔ)上的增量值和倍數(shù)值龄坪。增量值默認(rèn)是0妓局,而倍數(shù)值則默認(rèn)是1甥啄。比如某個(gè)子視圖的寬度等于另外一個(gè)子視圖的寬度值加20的時(shí)融虽,可以通過(guò)equal方法設(shè)置如下:
A.tg_width.equal(B.tg_width,?increment:20)??//A的寬度等于B的寬度加20
除了可以在equal方法中指定增量值外飘言,布局尺寸類還單獨(dú)提供一個(gè)add方法來(lái)實(shí)現(xiàn)增量值的設(shè)置:
public?func?add(_?val:CGFloat)?->TGLayoutSize
這樣上述的代碼也可以用如下的方式設(shè)置:
A.tg_width.equal(B.tg_width).add(20)
在equal方法中的multiple值則是指定尺寸等于另外一個(gè)尺寸的倍數(shù)值苛预。比如某個(gè)子視圖的高度等于另外一個(gè)子視圖的高度的一半時(shí),可以通過(guò)equal方法設(shè)置如下:
A.tg_height.equal(B.tg_height,?multiple:0.5);??//A的高度等于B的高度的一半丘薛。
除了可以在equal方法中指定倍數(shù)值外邦危,布局尺寸類還單獨(dú)提供一個(gè)multiply方法來(lái)實(shí)現(xiàn)倍數(shù)值的設(shè)置:
public?func?multiply(_?val:CGFloat)?->TGLayoutSize
這樣上述的代碼也可以用如下的方式設(shè)置:
A.tg_height.equal(B.tg_height).multiply(0.5)
在布局尺寸類中我們除了可以用equal, add, multiply方法來(lái)設(shè)置視圖的尺寸依賴值以及增量和倍數(shù)外,我們還可以對(duì)視圖尺寸的最大最小值進(jìn)行控制處理。比如在實(shí)踐中我們希望某個(gè)視圖的寬度等于另外一個(gè)兄弟視圖的寬度播急,但是最小不能小于20昌妹,而最大則不能超過(guò)父視圖的寬度的一半。這時(shí)候我們就需要用到布局尺寸類的另外兩個(gè)方法了:
public?func?min(_?size:CGFloat,?increment:CGFloat?=?0,?multiple:CGFloat?=?1)?->TGLayoutSize
public?func?min(_?dime:TGLayoutSize!,?increment:CGFloat?=?0,?multiple:CGFloat?=?1)?->TGLayoutSize
public?func?min(_?view:UIView,?increment:CGFloat?=?0,?multiple:CGFloat?=?1)?->TGLayoutSize
public?func?max(_?size:CGFloat,?increment:CGFloat?=?0,?multiple:CGFloat?=?1)?->TGLayoutSize
public?func?max(_?view:UIView,?increment:CGFloat?=?0,?multiple:CGFloat?=?1)?->TGLayoutSize
public?func?max(_?dime:TGLayoutSize!,?increment:CGFloat?=?0,?multiple:CGFloat?=?1)?->TGLayoutSize
上述的兩個(gè)方法min,max分別用來(lái)設(shè)置視圖尺寸最小不能小于的值以及最大不能超過(guò)的值牢裳。方法中我們可以看出最大最小值除了可以設(shè)置具體的數(shù)值外還可以設(shè)置為另外一個(gè)布局尺寸對(duì)象晦墙,同樣我們還可以設(shè)置增量和倍數(shù)值悦昵。因此我們可以通過(guò)對(duì)min和max方法的使用來(lái)解決上述的問(wèn)題:
//A的寬度等于B的寬度,最小為20晌畅,最大為父視圖寬度的一半旱捧。
A.tg_width.equal(B.tg_width).min(20).max(A.superview,multiple:0.5)
最后我們列出視圖的擴(kuò)展屬性tg_width, tg_height在各布局視圖下equal方法能夠設(shè)置的值的類型,我們這里設(shè)置B為一個(gè)兄弟視圖踩麦,S為父視圖
屬性/值CGFloat/TGWeight/wrap/fillA.tg_widthA.tg_heightB.tg_widthB.tg_heightS.tg_widthS.tg_height[TGLayoutSize]
A.tg_widthALL-FR/R/FLH/FOFR/R/FO/PRALLRR
A.tg_heightALLFR/R/FLV/FO/LV-RFR/R/FO/PRALLR
布局位置類TGLayoutPos
布局位置類用來(lái)描述視圖布局核心中的視圖的位置。我們對(duì)UIView擴(kuò)展出了6個(gè)布局位置對(duì)象:
public?var?tg_left:TGLayoutPos???????//視圖左邊布局位置
public?var?tg_top:TGLayoutPos??????//視圖上邊布局位置
public?var?tg_right:TGLayoutPos???//視圖右邊布局位置
public?var?tg_bottom:TGLayoutPos??//視圖下邊布局位置
public?var?tg_centerX:TGLayoutPos??//視圖水平中心點(diǎn)布局位置
public?var?tg_centerY:TGLayoutPos???//視圖垂直中心點(diǎn)布局位置
分別用來(lái)實(shí)現(xiàn)視圖的水平維度的左氓癌、中谓谦、右三個(gè)方位以及視圖垂直維度的上、中贪婉、下三個(gè)方位的布局位置設(shè)置反粥。在TGLayoutPos類中,我們可以通過(guò)方法equal來(lái)設(shè)置視圖位置的多種類型的值,類中是通過(guò)重載equal方法來(lái)實(shí)現(xiàn)多種類型的值的設(shè)置的才顿。
public?func?equal(_?origin:CGFloat,?offset:CGFloat?=?0)?->TGLayoutPos
public?func?equal(_?weight:TGWeight,?offset:CGFloat?=?0)?->TGLayoutPos
public?func?equal(_?array:[TGLayoutPos],?offset:CGFloat?=?0)?->TGLayoutPos
public?func?equal(_?view:?UIView,?offset:CGFloat?=?0)?->TGLayoutPos
public?func?equal(_?pos:TGLayoutPos!,?offset:CGFloat?=?0)?->TGLayoutPos
我們可以通過(guò)上面定義的equal方法來(lái)設(shè)置:
CGFloat類型的值表示視圖的位置是一個(gè)絕對(duì)值類型的位置值莫湘。 比如:
A.tg_left.equal(10)??????????//A視圖的左邊位置是10
A.tg_right.equal(20)????????//A視圖的右邊位置是20
A.tg_centerX.equal(5)?????//A視圖的水平中心點(diǎn)的偏移位置是5
我們知道在視圖定位時(shí)位置的概念根據(jù)參考坐標(biāo)系不同而不同:
對(duì)于絕對(duì)值類型的位置值,他所表示的意義是邊距還是間距這個(gè)要看他所加入的布局視圖的類型而不同郑气。下面的列表中展示了位置在不同的布局中描述的是間距還是邊距:
定位的值如果是以父視圖作為參考系坐標(biāo)那么視圖的位置就叫做邊距 ,邊距描述的是視圖距離父視圖的距離幅垮。
定位的值如果是以兄弟視圖作為參考系坐標(biāo)那么視圖的位置就叫做間距,間距描述的是視圖距離兄弟視圖的距離(垂直線性布局中雖然第一個(gè)子視圖的頂部是距離父視圖但是我們?nèi)匀环Q為間距)尾组。
位置/布局邊距間距
tg_left/tg_rightLV/FR/R/TH/SLH/FO/FL/P/TV
tg_top/tg_bottomLH/FR/R/TV/SLV/FO/FL/P/TH
tg_centerXLV/FR/R/TH/S-
tg_centerYLH/FR/R/TV/S-
TGWeight類型的值表示視圖的位置是一個(gè)依賴于父視圖尺寸的相對(duì)比例值忙芒。目前只有在線性布局、框架布局讳侨、和非布局父視圖中才支持這種類型的值的設(shè)置(具體見(jiàn)上面TGWeight類型值的定義和使用)
//假如A視圖是在一個(gè)垂直線性布局里面呵萨,垂直線性布局的寬度為50
A.tg_left.equal(20%)???//A視圖的左邊距占用父視圖寬度的20%也就是10
A.tg_right.equal(30%)??//A視圖的右邊距占用父視圖寬度的30%也就是15
TGLayoutPos類型的值表示視圖的位置依賴另外一個(gè)視圖的位置。這種類型的值大部分用于在相對(duì)布局中使用的子視圖跨跨,但是有幾個(gè)特殊的位置就是父視圖的位置是幾乎在所有布局視圖中都支持潮峦。比如:
A.tg_left.equal(B.tg_right)???//A視圖在B視圖的右邊
A.tg_top.equal(A.superview.tg_top)??//A視圖的頂部和父視圖對(duì)齊
A.tg_centerX.equal(B.tg_right)??????//A視圖的水平中心點(diǎn)和B視圖的右邊對(duì)齊
UIView類型的值其實(shí)就是TGLayoutPos的簡(jiǎn)化版本設(shè)置,標(biāo)識(shí)某個(gè)方位的位置等于指定視圖的相同方法的位置值勇婴。比如:
A.tg_left.equal(B)???//A的左邊位置和B的左邊位置相等
[TGLayoutPos]數(shù)組類型的值忱嘹,只能用在相對(duì)布局里面的子視圖的tg_centerX,tg_centerY這兩個(gè)屬性的equal方法中才有意義,他表示子視圖和數(shù)組里面其他所有子視圖的位置在相對(duì)布局中整體水平居中或者垂直居中咆耿。比如:
//相對(duì)布局里面有A,B,C,D四個(gè)子視圖德谅,想讓這四個(gè)子視圖在布局視圖里面整體水平居中。
A.tg_centerX.equal([B.tg_centerX,C.tg_centerX,D.tg_centerX])
A.tg_centerX.equal(B.tg_centerX)?//這個(gè)意義和上面是不同的萨螺,他表示A視圖的水平中心點(diǎn)和B視圖的水平中心點(diǎn)是對(duì)齊的窄做。
A.tg_centerX.equal([B.tg_centerX])?//這個(gè)表示A,B在布局視圖里面整體水平居中
上面列出了布局位置類中的equal方法可以設(shè)置的值的類型,我們還看到了方法中存在著另外一個(gè)默認(rèn)的參數(shù):offset這個(gè)參數(shù)的意義表示在位置等于上述類型的值的基礎(chǔ)上的偏移值慰技。偏移默認(rèn)是0椭盏。比如某個(gè)子視圖的左邊位置等于另外一個(gè)子視圖的右邊的位置再往右偏移20時(shí),可以通過(guò)equal方法設(shè)置如下:
A.tg_left.equal(B.tg_right,?offset:20)??//A在B視圖的右邊再往右偏移20
A.tg_top.equal(A.superview.tg_top,?offset:20)?//A在父視圖頂部往下偏移20的位置
除了可以在equal方法中指定偏移量值外吻商,布局位置類還單獨(dú)提供了一個(gè)offset方法來(lái)實(shí)現(xiàn)偏移量的設(shè)置:
public?func?offset(_?val:CGFloat)?->TGLayoutPos
這樣上述的代碼也可以用如下方法設(shè)置:
A.tg_left.equal(B.tg_right).offset(20)
A.tg_top.equal(A.superview.tg_top).offset(20)
通過(guò)偏移量的設(shè)置掏颊,我們可以發(fā)現(xiàn)那些表示的是邊距意義的位置值,其實(shí)就是等于位置依賴于父視圖對(duì)應(yīng)位置的偏移值艾帐。比如某個(gè)子視圖的左邊距是20乌叶,其實(shí)就是等價(jià)于子視圖的左邊等于父視圖的左邊再偏移20。下面的代碼其實(shí)是等價(jià)的柒爸。
//A是一個(gè)相對(duì)布局里面的子視圖
A.tg_left.equal(20)
A.tg_left.equal(A.superview.tg_left).offset(20)???//這句代碼和上句是等價(jià)的
A.tg_centerY.equal(0)
A.tg_centerY.equal(A.superview.tg_centerY).offset(0)?//這句代碼和上句是等價(jià)的
A.tg_bottom.equal(20)
A.tg_bottom.equal(A.superview.tg_bottom).offset(20)?//這句代碼和上句是等價(jià)的
在布局位置類中我們除了可以用equal,offset方法設(shè)置視圖的位置依賴及偏移量外准浴,我們還可以對(duì)視圖位置的最大最小值進(jìn)行控制處理。比如在實(shí)踐中我們希望某個(gè)子視圖的左邊距等于父視圖的寬度的20%捎稚,但是最小不能小于20乐横,最大不能超過(guò)30求橄。這時(shí)候我們就需要用到布局位置類的另外兩個(gè)方法了:
public?func?min(_?val:CGFloat,?offset:CGFloat?=?0)?->TGLayoutPos
public?func?max(_?val:CGFloat,?offset:CGFloat?=?0)?->TGLayoutPos
上述的兩個(gè)方法min,max分別用來(lái)設(shè)置視圖位置最小不能小于的值以及最大不能超過(guò)的值。方法中我們可以設(shè)置一個(gè)具體的數(shù)值以及偏移量葡公,因此我們可以通過(guò)對(duì)min和max方法的使用來(lái)解決上述的問(wèn)題:
//A的左邊距等于父視圖的寬度的20%罐农,最小為20,最大為30
A.tg_left.equal(20%).min(20).max(30)
最后我們列出子視圖的6個(gè)擴(kuò)展屬性在各布局視圖下equal方法能夠設(shè)置的值的類型:
屬性/值CGFloatTGWeightTGLayoutPos[TGLayoutPos]
tg_leftALLL/FR/T/R/SR-
tg_topALLL/FR/T/R/SR-
tg_rightALLL/FR/T/R/SR-
tg_bottomALLL/FR/T/R/SR-
tg_centerXALLL/FR/T/R/SRR
tg_centerYALLL/FR/T/R/SRR