前言:ConstraintLayout作為google欽定的代替LinearLayout及RelativeLayout的布局值得我們學(xué)習(xí)文兑,并且google仍舊想借此布局繼續(xù)改善之前布局可視化編輯上的雞肋效果箕般,但由于當(dāng)下版本新的可視化效果在布局編輯器上與實際運(yùn)行結(jié)果仍舊可能略有不同猪瞬,我們還是很難在不編輯代碼的情況下僅靠拖拽來編輯布局籽御,所以本文不會從拖拽入手话浇,而是從布局各種相關(guān)API的代碼層面學(xué)習(xí)該布局的使用
對于ConstraintLayout來講增蹭,為了更好的在實際應(yīng)用過程中使用它杆逗,有許多重要的概念及規(guī)則需要了解熟記,通過本文剪个,你不僅能看到ConstraintLayout官方說明的譯文秧骑,還將通過多個事例來模擬實際使用中容易遇見的問題,希望以拋磚引玉的方式引出實際應(yīng)用中可能遇到的坑扣囊,并加以避免
1.layout_constraintA_toBOf
1.1基礎(chǔ)概念
這里的A與B的取值分別為下圖的top乎折、left、right等侵歇,并有形如top-bottom骂澄、start-end的對應(yīng)關(guān)系,并且這種對應(yīng)關(guān)系是必然的惕虑,下面我們會說明原因
例如:
app:layout_constraintTop_toBottomOf="@+id/"
所表達(dá)的意思為令控件A的top邊與控件B的bottom邊共享同一塊相同的位置(share the same location)
代碼運(yùn)行結(jié)果為:
所以我們就能理解不可能出現(xiàn)layout_constraintTop_toLeftOf這樣的API坟冲,因為一個是豎邊磨镶,一個是橫邊,不可能共享同一塊位置
其他的API以此類推很好理解健提,那baseline的運(yùn)行結(jié)果會是如何琳猫?為了對比,我們先看如下代碼的運(yùn)行結(jié)果:
我們令B在A的右邊矩桂,然后設(shè)置B的寬高與字體大小大于A沸移,運(yùn)行結(jié)果如我們所預(yù)想:
然后我們設(shè)置:
app:layout_constraintBaseline_toBaselineOf="@+id/tv_a"
運(yùn)行結(jié)果會不會如我們所想呢:
觀察結(jié)果可知,由于B的高度大于A侄榴,所以B上面的一部分移動到了屏幕之外,并且由于B字體大小大于A网沾,所以即便是指定BaseLine在同一位置癞蚕,B的文字也要高于A
我們再修改代碼,為父布局加入paddingTop辉哥,看能否使被屏幕遮蓋的布局“移下來”:
觀察結(jié)果:
B移到了父布局的外部桦山,在父布局上加入padding會導(dǎo)致子布局的內(nèi)容整體下移,這與我們在其他布局中的經(jīng)驗是一致的醋旦,既然這樣的話恒水,若我們直接在A上設(shè)置marginTop能否讓A“帶著”B一起下來呢,我們繼續(xù)修改代碼:
運(yùn)行結(jié)果:
觀察結(jié)果我們發(fā)現(xiàn)A并沒有按照預(yù)想挪下來饲齐,究其原因是由于我們沒有為A布局設(shè)置約束(Constraint)钉凌,所以A不知道該如何偏移,我們?yōu)锳添加約束捂人,并將A的約束對象設(shè)置為父元素ConstraintLayout本身:
觀察代碼運(yùn)行結(jié)果:
綜上御雕,約束布局中,若期望設(shè)置某控件的margin來控制顯示位置滥搭,則必須指定該控件在該方向上的約束:
官方解釋為如果設(shè)置控件的margin酸纲,它會施加在存在的對應(yīng)約束上(If side margins are set, they will be applied to the corresponding constraints (if they exist))
1.2居中
查找發(fā)現(xiàn)API沒有為我們提供例如layoutGravity或centerInParent之類的方法讓我們把一個控件在約束布局內(nèi)居中,但是可以通過官方提供的方式來達(dá)到目的
指定讓控件的寬高與父布局重疊并不會令該控件鋪滿父布局瑟匆,除非父布局與控件擁有完全相同的大小闽坡,不然只會令控件在指定的方向上居中
當(dāng)控件布局成這種狀態(tài)下時,我們可以通過設(shè)置偏移(bias)來改變控件的默認(rèn)(50%)位置:
1.3由居中寫法引申而來的問題
有了以上的基礎(chǔ)概念愁溜,我們來分析幾個典型的例子疾嗅,在進(jìn)一步理解的同時也能發(fā)現(xiàn)其中的一些問題
1.3.1
上面居中代碼中,我們將A的左右邊與父布局的左右邊設(shè)定約束祝谚,最終的結(jié)果為A居中于父布局宪迟,若我們將A的右邊與B的右邊設(shè)定約束,效果會如何呢:
運(yùn)行結(jié)果:
可見A被從父布局的左邊“推離”了交惯,而在與B大小相等的位置“居中”次泽,此時我們便能隱約開始理解網(wǎng)上一些其他教程在翻譯官方說明時對于原文(What happens in this case is that the constraints act like opposite forces pulling the widget apart equally)的譯文的含義了穿仪。
1.3.2
假設(shè)我們有如下代碼:
我們將A的左邊與父容器的左邊設(shè)定約束,然后將A的右邊與B的左邊設(shè)定約束意荤,此時A既約束于父容器也約束于B容器啊片,然后將B的右邊與A的左邊相約束戒傻,然后設(shè)置A的左右margin為20dip瓦戚,設(shè)置B在4個方向上的margin也為20dp,運(yùn)行結(jié)果為:
我們發(fā)現(xiàn)A與B之間的位置關(guān)系是正確的纬凤,B控件只有左間距捐寥,這也是正確的笤昨,符合我們上面得出的間距只在約束方向上有效的結(jié)論,但是A控件的左右間距都失效了握恳,感覺有點奇怪有問題瞒窒,但此處其實我們是創(chuàng)建了一種特殊的約束布局結(jié)構(gòu)--鏈(chain),對于鏈的定義乡洼、介紹以及為何margin感覺失效的問題我們下面會詳細(xì)說明崇裁,此處先有個印象。但如果我們此時去掉B相對于A的約束束昵,僅依靠A相對于B的約束能否保持這個位置不變呢拔稳,為了方便觀察結(jié)果,我們將A與B在布局文件中書寫的順序互換:
運(yùn)行結(jié)果為:
可見锹雏,A控件在嘗試以一個詭異的位置“居中”巴比,并且B的位置并沒有依照所想能在A的右邊,究其原理逼侦,必然是約束布局內(nèi)部的計算方法導(dǎo)致其在此類情況下計算出可居中的寬度及位置并嘗試居中匿辩,具體的算法還有待翻看源碼后總結(jié),此處暫時存疑并有待補(bǔ)充榛丢,接下來我們修改間距大胁颉:
觀察結(jié)果:
我們可以確認(rèn)一點,在這種情況下晰赞,對A布局設(shè)置的margin是有效的稼病,但前提是我們沒有將A與B相互約束。
2.約束對象可見度為GONE的情況:
約束布局中掖鱼,我們可以通過設(shè)置:
layout_goneMarginX
屬性然走,來設(shè)置約束的控件對象可見度為GONE的情況下的margin數(shù)值
具體用法非常好理解,我們不再贅述戏挡。但值得注意的是芍瑞,約束布局下被設(shè)置為GONE的元素依舊是布局的一部分,與其他布局的區(qū)別在于其尺寸變?yōu)榱?褐墅,而不是脫離布局
3.約束布局內(nèi)子控件的寬高設(shè)置
官方說明常用方式為:
a.設(shè)置具體的寬高數(shù)值
b.設(shè)置寬高為wrap_content
c.設(shè)置寬高為0dp拆檬,以代表MATCH_CONSTRAINT
對于c方式洪己,約束布局中不能直接使用match_parent來設(shè)置控件的寬高,直接設(shè)置后IDE會自動轉(zhuǎn)換成0dp竟贯,這里我們有必要舉個例子與上面的居中例子做對比來說明0dp如何MATCH_CONSTRAINT
觀察結(jié)果:
C控件并沒有如我們所想match答捕,我們修改C的屬性:
運(yùn)行效果:
我們再思考一個例子,假設(shè)我們在此基礎(chǔ)上再加入一個控件D屑那,我們希望此控件位于A的正下方拱镐,并且寬度與A相同,我們寫下了如下的代碼:
運(yùn)行效果:
發(fā)現(xiàn)D在指定的兩條邊中間居中持际,符合我們上面的測試結(jié)果沃琅,現(xiàn)在我需要令D的寬與A一致,修改代碼:
運(yùn)行結(jié)果:
可以發(fā)現(xiàn)选酗,0dp指的是MATCH_CONSTRAINT阵难,而不是match_parent,需重點理解
d.設(shè)置ratio屬性
通過使用:
app:layout_constraintDimensionRatio="X,A:B"
屬性芒填,可以用比例的形式更靈活的指定控件的寬高,意思為系統(tǒng)會以最大的尺寸為依據(jù)空繁,加之寬高比殿衰,計算得出0dp所對應(yīng)的數(shù)值,其中X指的是要約束的邊盛泡,取值為W或H 且可以不填闷祥,A與B為具體的比例數(shù)值,代表寬:高傲诵,我們繼續(xù)修改控件D的屬性以具體查看:
我們設(shè)置D的寬為0dp凯砍,以告知系統(tǒng)寬需要計算得出,高設(shè)置為wrap_content拴竹,然后設(shè)置ratio的值為16:9悟衩,運(yùn)行結(jié)果:
控件D以最大尺寸的邊 高為基準(zhǔn),加之以寬高比16:9共同計算出寬度栓拜,我們繼續(xù)修改代碼:
控件D的寬高均置為0dp座泳,代表均需計算得出,然后指定寬高比為16:9幕与,此時系統(tǒng)會設(shè)置最大尺寸以滿足所有約束并保證寬高比:
在此基礎(chǔ)上挑势,我們?yōu)閞atio添加要約束的邊,
運(yùn)行效果:
此時我們的意思是寬作為最大尺寸啦鸣,加之寬高比16:9潮饱,計算出的高效果與不添加一致,若我們改為約束寬:
運(yùn)行效果:
可以看出诫给,當(dāng)我們選擇約束寬時香拉,意思變?yōu)閷捵鳛樽畲蟪叽缋惭铮又?b>高寬比為16:9,計算出的高會比寬要大
既然如此缕溉,那就說明此種情況下寬高的大小是互為影響的考传,我們繼續(xù)修改代碼:
我們將D依賴約束的控件A的寬增加至80dp,觀察效果:
D的寬隨著A的增大而增大证鸥,這同時也會導(dǎo)致D的高隨之增大僚楞,這也是實際應(yīng)用中需要注意的一點
4.鏈(chain)
鏈?zhǔn)且环N特殊的約束布局結(jié)構(gòu),有點像數(shù)據(jù)結(jié)構(gòu)中的雙向鏈表枉层,其定義就是多個控件相互約束而形成的結(jié)構(gòu)泉褐,并擁有頭(head)的概念:
鏈擁有4種風(fēng)格(style):
a.CHAIN_SPREAD:默認(rèn),鏈會正常的分離開(spread out)
b.CHAIN_SPREAD_INSIDE:與默認(rèn)風(fēng)格類似鸟蜡,但此種模式下的鏈頭及鏈尾不會分離開
c.CHAIN_PACKED:此模式下的控件會被打包(packed)在一起膜赃,可以理解為擠在一起而不是分離開,此時指定bias屬性可以改變默認(rèn)的偏移量揉忘,類似我們在居中小節(jié)中介紹的那樣
d.Weighted chain:權(quán)重鏈跳座,在默認(rèn)風(fēng)格的情況下,如果某一個控件使用0dp設(shè)置寬高泣矛,即被設(shè)置為MATCH_CONSTRAINT疲眷,則叫做權(quán)重鏈(*網(wǎng)絡(luò)上有其他教程將其譯作加權(quán)鏈,這是非常明顯的直接套用百度翻譯的結(jié)果您朽,按照官方定義的理解狂丝,此模式更像我們在LinearLayout中使用的權(quán)重(weight)屬性,所以我們依此思路將其叫做權(quán)重鏈更符合原意)哗总。
還記得我們在居中小節(jié)測試代碼現(xiàn)象時几颜,無意中寫出了這種互相約束的鏈結(jié)構(gòu),并在設(shè)置鏈頭的margin時發(fā)現(xiàn)是無效的讯屈,對此官方給出了說明:
在連接處設(shè)定的margin會被計算在內(nèi)蛋哭,而對于spread(默認(rèn))模式下的鏈,設(shè)定的margin會被扣除(If margins are specified on connections, they will be taken in account. In the case of spread chains, margins will be deducted from the allocated space)
按照說明耻煤,CHAIN_SPREAD模式下對鏈頭設(shè)置的margin是無效的具壮,那其他模式應(yīng)該是有效的,我們驗證一下:
上面代碼我們通過指定控件A和C的寬為0dp(MATCH_CONSTRAINT)的形式創(chuàng)建了一組權(quán)重鏈哈蝇,按照官方說明棺妓,非默認(rèn)鏈的情況下鏈頭的margin應(yīng)該是有效的,觀察代碼結(jié)果:
結(jié)果確是如我們所想炮赦,控件A由于只在左右兩邊設(shè)置了約束怜跑,所以設(shè)置的20dp只生效于左右兩邊。
最后,對于其他模式我們沒有太多需要說明性芬,但對于權(quán)重鏈來講峡眶,當(dāng)多個控件被指定為0dp即MATCH_CONSTRAINT后,默認(rèn)它們將用可用空間平分彼此植锉,但你同樣可以使用layout_constraintHorizontal_weight或layout_constraintVertical_weight屬性來指定它們的權(quán)重辫樱,就像我們在LinearLayout中做的那樣,權(quán)重規(guī)則也與LinearLayout中的weight屬性相同俊庇,具體效果我們就不再贅述了狮暑。
5.參照線(Guideline)
Guideline非常像我們PS中經(jīng)常使用的標(biāo)尺,作用也非常相似辉饱,同樣可以用來幫助我們定位控件 但更強(qiáng)大搬男,我們舉個很簡單的例子,如果我現(xiàn)在約束布局中只有一個控件彭沼,那它會因只有父布局可以約束而無法布局在屏幕中的位置缔逛,此時我們便可以設(shè)置Guideline并令控件相對于其做約束來控制控件的位置,Guideline原理上是一個被設(shè)置成View.GONE且寬或高為0的控件姓惑,所以你不會在頁面上真正的看到它褐奴,當(dāng)然Guideline的作用可不僅僅這么簡單,利用Guideline可以讓我們的布局工作變得更加靈活及高效于毙,我們先嘗試一下簡單的例子了解它,另外的章節(jié)我們會結(jié)合實際項目去掌握它望众。
我們可以通過
android:orientation="X"
屬性來在指定方向創(chuàng)建一條Guideline烂翰,X可指定為vertical及horizontal,分別對應(yīng)橫向及豎向的Guideline甘耿,再結(jié)合
app:layout_constraintGuide_X
屬性來指定Guideline來的位置,X可指定為begin佳恬、end及percent,根據(jù)Guideline方向的不同分別代表從上(左)毁葱、下(右)及百分比贰剥,屬性的值代表具體的位置數(shù)值:
我們創(chuàng)建了2條Guideline,一條橫向一條豎向蚌成,并設(shè)置位置為100dp凛捏,然后令控件A約束于它們芹缔,觀察效果:
可以看到坯癣,控件A顯示在了我們期望的位置
結(jié)語:終于,我們深入學(xué)習(xí)完了有關(guān)于ConstraintLayout常用的屬性最欠,也調(diào)試了這些屬性在各種情況下的效果示罗,ConstraintLayout的作用并不僅僅是簡單融合并代替線性布局與相對布局,更是一種全新的布局思想窒所,這種思想甚至將影響我們在頁面動畫動效上的設(shè)計鹉勒,在下一篇文章中,我們將結(jié)合實例去嘗試使用代碼來動態(tài)構(gòu)建ConstraintLayout吵取,并接觸ConstraintLayout真正有用有趣的用法禽额。