使用的ConstraintLayout版本
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
如果不使用androidx
的話可以使用下面的版本
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
注意:使用不同的ConstraintLayout版本可能會(huì)有坑踱阿,如果在使用過(guò)程中發(fā)現(xiàn)實(shí)現(xiàn)不了想要添加的約束,可以嘗試改變ConstraintLayout的版本如上所示歼冰。
1. 動(dòng)態(tài)添加View
第一種情況:所有的View都是動(dòng)態(tài)添加
舉個(gè)例子
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/clRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:id="@+id/ivLeft"
android:layout_width="100dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_lake"
app:layout_constraintDimensionRatio="h,16:9"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvRight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="@string/lake_tahoe_title"
android:textSize="30sp"
app:layout_constraintLeft_toRightOf="@+id/ivLeft"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvBottom"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="8dp"
android:text="@string/lake_discription"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/ivLeft" />
</androidx.constraintlayout.widget.ConstraintLayout>
上面的布局文件中呈現(xiàn)的效果如圖所示,接下來(lái)我們用代碼的方式動(dòng)態(tài)添加View,實(shí)現(xiàn)上面的效果粤咪。
首先在res/values
文件夾下新建一個(gè)ids.xml
,在ids.xml
中聲明我們要添加的View的控件id。
ids.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="clRoot" type="id" />
<item name="ivLeft" type="id" />
<item name="tvRight" type="id" />
<item name="tvBottom" type="id" />
</resources>
然后開(kāi)始寫(xiě)代碼
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
addViewUseLayoutParams()
}
使用ConstraintLayout.LayoutParams
private fun addViewUseLayoutParams() {
val constraintLayout = ConstraintLayout(this)
constraintLayout.id = R.id.clRoot
constraintLayout.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
//先設(shè)置根布局
setContentView(constraintLayout)
val ivLeft = ImageView(this)
ivLeft.id = R.id.ivLeft
ivLeft.scaleType = ImageView.ScaleType.CENTER_CROP
ivLeft.setImageResource(R.drawable.ic_lake)
val ivLeftLayoutParams: ConstraintLayout.LayoutParams = ConstraintLayout.LayoutParams(
ScreenUtil.dpToPx(this, 100), 0
)
ivLeftLayoutParams.leftToLeft = R.id.clRoot
ivLeftLayoutParams.marginStart = ScreenUtil.dpToPx(this, 8)
ivLeftLayoutParams.topToTop = R.id.clRoot
ivLeftLayoutParams.topMargin = ScreenUtil.dpToPx(this, 8)
ivLeftLayoutParams.dimensionRatio = "h,16:9"
ivLeft.layoutParams = ivLeftLayoutParams
val tvRight = TextView(this)
tvRight.id = R.id.tvRight
tvRight.text = getString(R.string.lake_tahoe_title)
tvRight.textSize = 30F
val tvRightLayoutParams: ConstraintLayout.LayoutParams = ConstraintLayout.LayoutParams(
ConstraintLayout.LayoutParams.WRAP_CONTENT,
ConstraintLayout.LayoutParams.WRAP_CONTENT
)
tvRightLayoutParams.startToEnd = R.id.ivLeft
tvRightLayoutParams.topToTop = R.id.clRoot
tvRightLayoutParams.marginStart = ScreenUtil.dpToPx(this, 16)
tvRightLayoutParams.topMargin = ScreenUtil.dpToPx(this, 16)
tvRight.layoutParams = tvRightLayoutParams
val tvBottom = TextView(this)
tvBottom.id = R.id.tvBottom
tvBottom.text = getString(R.string.lake_discription)
val tvBottomLayoutParams: ConstraintLayout.LayoutParams = ConstraintLayout.LayoutParams(
0,
ConstraintLayout.LayoutParams.WRAP_CONTENT
)
tvBottomLayoutParams.startToStart = R.id.clRoot
tvBottomLayoutParams.marginStart = ScreenUtil.dpToPx(this, 8)
tvBottomLayoutParams.endToEnd = R.id.clRoot
tvBottomLayoutParams.marginEnd = ScreenUtil.dpToPx(this, 8)
tvBottomLayoutParams.topToBottom = R.id.ivLeft
tvBottomLayoutParams.topMargin = ScreenUtil.dpToPx(this, 24)
tvBottom.layoutParams = tvBottomLayoutParams
constraintLayout.addView(ivLeft)
constraintLayout.addView(tvRight)
constraintLayout.addView(tvBottom)
}
效果和上面是一樣的渴杆,就不截圖了寥枝。在上面的方法中宪塔,我們是使用ConstraintLayout.LayoutParams
來(lái)實(shí)現(xiàn)添加view并指定約束的。接下來(lái)囊拜,我們換一種方式某筐,使用ConstraintSet
來(lái)添加view并指定約束。關(guān)于ConstraintSet
的介紹請(qǐng)參考 ConstraintSet冠跷。
使用ConstraintSet
private fun addViewUseConstraintSet() {
val constraintLayout = ConstraintLayout(this)
constraintLayout.id = R.id.clRoot
constraintLayout.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
//先設(shè)置根布局
setContentView(constraintLayout)
val constraintSet = ConstraintSet()
val ivLeft = ImageView(this)
ivLeft.id = R.id.ivLeft
ivLeft.scaleType = ImageView.ScaleType.CENTER_CROP
ivLeft.setImageResource(R.drawable.ic_lake)
constraintSet.constrainWidth(R.id.ivLeft, ScreenUtil.dpToPx(this, 100))
constraintSet.constrainHeight(R.id.ivLeft, 0)
constraintSet.setDimensionRatio(R.id.ivLeft, "h,16:9")
//layout_constraintTop_toTopOf
constraintSet.connect(
R.id.ivLeft, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP,
ScreenUtil.dpToPx(this, 16)
)
constraintSet.connect(
R.id.ivLeft, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START,
ScreenUtil.dpToPx(this, 16)
)
val tvRight = TextView(this)
tvRight.id = R.id.tvRight
tvRight.text = getString(R.string.lake_tahoe_title)
tvRight.textSize = 30F
constraintSet.constrainHeight(R.id.tvRight, ConstraintLayout.LayoutParams.WRAP_CONTENT)
constraintSet.constrainWidth(R.id.tvRight, ConstraintLayout.LayoutParams.WRAP_CONTENT)
constraintSet.connect(
R.id.tvRight, ConstraintSet.START, R.id.ivLeft, ConstraintSet.END,
ScreenUtil.dpToPx(this, 16)
)
constraintSet.connect(
R.id.tvRight, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP,
ScreenUtil.dpToPx(this, 16)
)
val tvBottom = TextView(this)
tvBottom.id = R.id.tvBottom
tvBottom.text = getString(R.string.lake_discription)
//設(shè)置高度
constraintSet.constrainHeight(R.id.tvBottom, ConstraintLayout.LayoutParams.WRAP_CONTENT)
constraintSet.connect(
R.id.tvBottom, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START,
ScreenUtil.dpToPx(this, 8)
)
constraintSet.connect(
R.id.tvBottom, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END,
ScreenUtil.dpToPx(this, 8)
)
constraintSet.connect(
R.id.tvBottom, ConstraintSet.TOP, R.id.ivLeft, ConstraintSet.BOTTOM,
ScreenUtil.dpToPx(this, 24)
)
constraintLayout.addView(ivLeft)
constraintLayout.addView(tvRight)
constraintLayout.addView(tvBottom)
TransitionManager.beginDelayedTransition(constraintLayout)
constraintSet.applyTo(constraintLayout)
}
效果也是一樣的南誊。
第二種情況,動(dòng)態(tài)添加個(gè)別View,感覺(jué)這種場(chǎng)景應(yīng)該不多
在上面的例子中蜜托,我們假設(shè)tvBottom已經(jīng)在布局中了抄囚。
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/clRoot"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tvBottom"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="8dp"
android:text="@string/lake_discription"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
接下來(lái),我們動(dòng)態(tài)的把ivLeft
和tvRight
添加到布局中去盗冷,實(shí)現(xiàn)和第一個(gè)例子中同樣的效果怠苔。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//布局文件別忘了
setContentView(R.layout.activity_main)
addPartView()
}
private fun addPartView() {
val ivLeft = ImageView(this)
ivLeft.id = R.id.ivLeft
ivLeft.scaleType = ImageView.ScaleType.CENTER_CROP
ivLeft.setImageResource(R.drawable.ic_lake)
val ivLeftLayoutParams: ConstraintLayout.LayoutParams = ConstraintLayout.LayoutParams(
ScreenUtil.dpToPx(this, 100), 0
)
ivLeftLayoutParams.dimensionRatio = "h,16:9"
ivLeftLayoutParams.topMargin = ScreenUtil.dpToPx(this, 16)
ivLeftLayoutParams.marginStart = ScreenUtil.dpToPx(this, 16)
ivLeftLayoutParams.leftToLeft = R.id.clRoot
ivLeftLayoutParams.topToTop = R.id.clRoot
ivLeft.layoutParams = ivLeftLayoutParams
val tvRight = TextView(this)
tvRight.id = R.id.tvRight
tvRight.text = getString(R.string.lake_tahoe_title)
tvRight.textSize = 30F
val tvRightLayoutParams: ConstraintLayout.LayoutParams = ConstraintLayout.LayoutParams(
ConstraintLayout.LayoutParams.WRAP_CONTENT,
ConstraintLayout.LayoutParams.WRAP_CONTENT
)
tvRightLayoutParams.startToEnd = R.id.ivLeft
tvRightLayoutParams.topToTop = R.id.clRoot
tvRightLayoutParams.marginStart = ScreenUtil.dpToPx(this, 16)
tvRightLayoutParams.topMargin = ScreenUtil.dpToPx(this, 16)
tvRight.layoutParams = tvRightLayoutParams
val tvBottomLayoutParams: ConstraintLayout.LayoutParams = ConstraintLayout.LayoutParams(
0,
ConstraintLayout.LayoutParams.WRAP_CONTENT
)
tvBottomLayoutParams.startToStart = R.id.clRoot
tvBottomLayoutParams.marginStart = ScreenUtil.dpToPx(this, 8)
tvBottomLayoutParams.endToEnd = R.id.clRoot
tvBottomLayoutParams.marginEnd = ScreenUtil.dpToPx(this, 8)
tvBottomLayoutParams.topToBottom = R.id.ivLeft
tvBottomLayoutParams.topMargin = ScreenUtil.dpToPx(this, 24)
tvBottomLayoutParams.bottomMargin = ScreenUtil.dpToPx(this, 8)
//重新為布局中已經(jīng)存在的tvBottom設(shè)置新的布局參數(shù)。
tvBottom.layoutParams = tvBottomLayoutParams
clRoot.addView(ivLeft)
clRoot.addView(tvRight)
}
這種方式要注意重新為布局中已經(jīng)存在的控件設(shè)置新的布局參數(shù)仪糖。
動(dòng)態(tài)改變約束
如果我們想動(dòng)態(tài)改變布局中的View的約束該怎么做呢柑司?比如我們想把上面的布局樣式改成下圖所示。
其實(shí)锅劝,在上面我們已經(jīng)給tvBottom動(dòng)態(tài)改變約束了攒驰,就是給View重新設(shè)置布局參數(shù)就好了。
給View重新設(shè)置布局參數(shù)
下面我們?cè)诖a中故爵,重新改變View的布局參數(shù)玻粪。
private fun changeLayoutParams() {
val ivLeftLayoutParams: ConstraintLayout.LayoutParams = ConstraintLayout.LayoutParams(
0, 0
)
ivLeftLayoutParams.leftMargin = ScreenUtil.dpToPx(this, 16)
ivLeftLayoutParams.rightMargin = ScreenUtil.dpToPx(this, 16)
ivLeftLayoutParams.topMargin = ScreenUtil.dpToPx(this, 16)
ivLeftLayoutParams.leftToLeft = R.id.clRoot
ivLeftLayoutParams.rightToRight = R.id.clRoot
ivLeftLayoutParams.dimensionRatio = "h,16:9"
ivLeftLayoutParams.topToTop = R.id.clRoot
//修改布局參數(shù)
ivLeft.layoutParams = ivLeftLayoutParams
val tvRightLayoutParams: ConstraintLayout.LayoutParams = ConstraintLayout.LayoutParams(
ConstraintLayout.LayoutParams.WRAP_CONTENT,
ConstraintLayout.LayoutParams.WRAP_CONTENT
)
tvRightLayoutParams.leftToLeft = R.id.clRoot
tvRightLayoutParams.topToBottom = R.id.ivLeft
tvRightLayoutParams.marginStart = ScreenUtil.dpToPx(this, 16)
tvRightLayoutParams.topMargin = ScreenUtil.dpToPx(this, 16)
tvRight.layoutParams = tvRightLayoutParams
val tvBottomLayoutParams: ConstraintLayout.LayoutParams = ConstraintLayout.LayoutParams(
0,
ConstraintLayout.LayoutParams.WRAP_CONTENT
)
tvBottomLayoutParams.startToStart = R.id.clRoot
tvBottomLayoutParams.marginStart = ScreenUtil.dpToPx(this, 8)
tvBottomLayoutParams.endToEnd = R.id.clRoot
tvBottomLayoutParams.marginEnd = ScreenUtil.dpToPx(this, 8)
tvBottomLayoutParams.topToBottom = R.id.tvRight
tvBottomLayoutParams.topMargin = ScreenUtil.dpToPx(this, 24)
tvBottomLayoutParams.bottomMargin = ScreenUtil.dpToPx(this, 8)
tvBottom.layoutParams = tvBottomLayoutParams
}
改變后的效果就不貼了。
使用ConstraintSet 動(dòng)態(tài)修改約束
使用ConstraintSet 動(dòng)態(tài)修改約束分四步诬垂。
- 首先要聲明一下ConstraintSet對(duì)象
val constraintSet = ConstraintSet()
- 復(fù)制一份現(xiàn)有的約束關(guān)系劲室,這一步不是必須的。
//從一個(gè)constraintLayout中復(fù)制約束
set.clone(constraintLayout: ConstraintLayout);
//從一個(gè)ConstraintSet中復(fù)制約束
set.clone(set: ConstraintSet);
//從一個(gè)布局文件中復(fù)制約束
set.clone(context: Context, constraintLayoutId: Int);
如果說(shuō)你要改變布局中某些控件的約束结窘,但是還要保存其他控件的約束關(guān)系很洋,那么你就需要從已有的根布局中復(fù)制一份約束,然后只更改哪些需要改變的控件的約束關(guān)系隧枫。
注意復(fù)制約束關(guān)系的時(shí)候喉磁,布局中的每個(gè)控件必都有id
,不然會(huì)報(bào)下面的錯(cuò)誤官脓。
java.lang.RuntimeException: All children of ConstraintLayout must have ids to use ConstraintSet
- 設(shè)置控件之間的約束
- 應(yīng)用新的約束协怒。在應(yīng)用約束的時(shí)候,為了讓約束改變的時(shí)候不是那么突兀卑笨,我們可以設(shè)置一個(gè)動(dòng)畫(huà)孕暇,來(lái)讓約束改變平滑一點(diǎn)。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_dynamic_add_view)
ivLeft.setOnClickListener {
changeConstraintSet()
}
}
private fun changeConstraintSet() {
val constraintSet = ConstraintSet()
//從根布局中克隆約束參數(shù)
constraintSet.clone(clRoot)
//清空控件原有的約束
constraintSet.clear(R.id.ivLeft)
constraintSet.clear(R.id.tvRight)
constraintSet.clear(R.id.tvBottom)
constraintSet.constrainWidth(R.id.ivLeft, 0)
constraintSet.constrainHeight(R.id.ivLeft, 0)
//設(shè)置ivLeft頂部和父布局頂部對(duì)齊
constraintSet.connect(
R.id.ivLeft, ConstraintSet.TOP, R.id.clRoot, ConstraintSet.TOP,
ScreenUtil.dpToPx(this, 16)
)
constraintSet.connect(
R.id.ivLeft, ConstraintSet.START, R.id.clRoot, ConstraintSet.START,
ScreenUtil.dpToPx(this, 16)
)
constraintSet.connect(
R.id.ivLeft, ConstraintSet.END, R.id.clRoot, ConstraintSet.END,
ScreenUtil.dpToPx(this, 16)
)
//設(shè)置寬高比
constraintSet.setDimensionRatio(R.id.ivLeft, "h,16:9")
constraintSet.constrainWidth(R.id.tvRight, ConstraintLayout.LayoutParams.WRAP_CONTENT)
constraintSet.constrainHeight(R.id.tvRight, ConstraintLayout.LayoutParams.WRAP_CONTENT)
constraintSet.connect(
R.id.tvRight, ConstraintSet.TOP, R.id.ivLeft, ConstraintSet.BOTTOM,
ScreenUtil.dpToPx(this, 24)
)
constraintSet.connect(
R.id.tvRight, ConstraintSet.START, R.id.clRoot, ConstraintSet.START,
ScreenUtil.dpToPx(this, 8)
)
constraintSet.constrainHeight(R.id.tvBottom, ConstraintLayout.LayoutParams.WRAP_CONTENT)
constraintSet.connect(
R.id.tvBottom, ConstraintSet.START, R.id.clRoot, ConstraintSet.START,
ScreenUtil.dpToPx(this, 8)
)
constraintSet.connect(
R.id.tvBottom, ConstraintSet.END, R.id.clRoot, ConstraintSet.END,
ScreenUtil.dpToPx(this, 8)
)
constraintSet.connect(
R.id.tvBottom, ConstraintSet.TOP, R.id.tvRight, ConstraintSet.BOTTOM,
ScreenUtil.dpToPx(this, 24)
)
constraintSet.applyTo(clRoot)
//設(shè)置一個(gè)動(dòng)畫(huà)效果,讓約束改變平滑一點(diǎn)妖滔,這一步不是必須的
TransitionManager.beginDelayedTransition(clRoot)
}
效果如下所示
遇到的一個(gè)問(wèn)題
在測(cè)試的時(shí)候派草,我想添加一個(gè)水平方向上的Guideline
,讓它在父布局豎直方向比例為0.4的地方铛楣,然后在Guideline
之上添加一個(gè)ImageView
。代碼如下
private fun addGuideLine() {
val constraintLayout = ConstraintLayout(this)
constraintLayout.id = R.id.clRoot
constraintLayout.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT)
//先設(shè)置根布局
setContentView(constraintLayout)
val guideline = Guideline(this)
guideline.id = R.id.guideline
val guideLayoutParams: ConstraintLayout.LayoutParams = ConstraintLayout.LayoutParams(
ConstraintLayout.LayoutParams.WRAP_CONTENT, ConstraintLayout.LayoutParams.WRAP_CONTENT)
guideLayoutParams.guidePercent = 0.4f
guideLayoutParams.topToTop = R.id.clRoot
guideLayoutParams.bottomToBottom = R.id.clRoot
//注意
guideLayoutParams.orientation = ConstraintLayout.LayoutParams.VERTICAL
guideline.layoutParams = guideLayoutParams
constraintLayout.addView(guideline)
val ivLeft = ImageView(this)
ivLeft.id = R.id.ivLeft
ivLeft.scaleType = ImageView.ScaleType.CENTER_CROP
ivLeft.setImageResource(R.drawable.lake)
val ivLeftLayoutParams: ConstraintLayout.LayoutParams = ConstraintLayout.LayoutParams(
0, 0)
ivLeftLayoutParams.dimensionRatio = "h,16:9"
ivLeftLayoutParams.bottomToTop = R.id.guideline
ivLeftLayoutParams.startToStart = R.id.clRoot
ivLeftLayoutParams.endToEnd = R.id.clRoot
ivLeft.layoutParams = ivLeftLayoutParams
constraintLayout.addView(ivLeft)
}
在測(cè)試的時(shí)候報(bào)了一個(gè)錯(cuò)誤
java.lang.AssertionError: TOP at android.support.constraint.solver.widgets.Guideline.getAnchor(Guideline.java:159)
折騰了半天艺普,發(fā)現(xiàn)是Guideline
的方向?qū)戝e(cuò)了簸州。
guideLayoutParams.orientation = ConstraintLayout.LayoutParams.VERTICAL
正確的寫(xiě)法
guideLayoutParams.orientation = ConstraintLayout.LayoutParams.HORIZONTAL
如果Guideline
的方向?qū)戝e(cuò)了,會(huì)導(dǎo)致依賴Guideline
的方向的控件的約束無(wú)法正確指定歧譬,所以會(huì)報(bào)錯(cuò)岸浑。如果遇到類似的問(wèn)題請(qǐng)仔細(xì)檢查,是否正確的設(shè)置了約束瑰步。
參考鏈接