背景交代
布局中,约束左、顶方向,左相对100dp,顶相对100dp。
代码中想要修改到:左相对200dp。
处理不对可能会失效,布局和逻辑列出供查看。
环境:constraintlayout版本2.1.1
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
简单原因放在后面。
class FragmentDemo : Fragment() {
companion object {
fun newInstance(): FragmentDemo {
return FragmentDemo()
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_demo, container, false)
initView(view)
return view
}
private fun initView(view: View) {
val layout = view.findViewById<ConstraintLayout>(R.id.layout_root)
view.findViewById<View>(R.id.iv_click).setOnClickListener {
val constraintSet = ConstraintSet()
//1、子View布局参数保存
constraintSet.clone(layout)
//2、移除约束
constraintSet.clear(R.id.iv_demo, ConstraintSet.LEFT)
//3、重新建立约束
constraintSet.connect(R.id.iv_demo, ConstraintSet.LEFT, R.id.layout_root, ConstraintSet.LEFT, 200)
//4、布局更新
constraintSet.applyTo(layout)
}
}
}
失效解决
移除相关约束constraintSet.clear(R.id.iv_demo, ConstraintSet.START)
class FragmentDemo : Fragment() {
...此处节约流量,删减部分代码...
private fun initView(view: View) {
val layout = view.findViewById<ConstraintLayout>(R.id.layout_root)
view.findViewById<View>(R.id.iv_click).setOnClickListener {
val constraintSet = ConstraintSet()
//1、子View布局参数保存
constraintSet.clone(layout)
//2、移除约束
constraintSet.clear(R.id.iv_demo, ConstraintSet.LEFT)
//3、再移除相关约束
constraintSet.clear(R.id.iv_demo, ConstraintSet.START)
//4、重新建立约束
constraintSet.connect(R.id.iv_demo, ConstraintSet.LEFT, R.id.layout_root, ConstraintSet.LEFT, 200)
//5、布局更新
constraintSet.applyTo(layout)
}
}
}
下面是基本分析逻辑
约束流程
参数保存
constraintSet.clone(layout)
子View布局参数保存
//ConstraintSet.java
public void clone(ConstraintLayout constraintLayout) {
int count = constraintLayout.getChildCount();
mConstraints.clear();
for (int i = 0; i < count; i++) {
...
//初始化约束参数Map,用于记录子View的参数
int id = view.getId();
if (!mConstraints.containsKey(id)) {
mConstraints.put(id, new Constraint());
}
ConstraintLayout.LayoutParams param = (ConstraintLayout.LayoutParams) view.getLayoutParams();
...
constraint.fillFrom(id, param);
}
}
//布局参数拷贝
private void fillFrom(int viewId, ConstraintLayout.LayoutParams param) {
layout.leftToLeft = param.leftToLeft;
layout.leftMargin = param.leftMargin;
...
int currentApiVersion = android.os.Build.VERSION.SDK_INT;
if (currentApiVersion >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
//startMargin初始化,逻辑在ViewGroup.java中
layout.startMargin = param.getMarginStart();
}
}
//ViewGroup.java
public int getMarginStart() {
if (startMargin != DEFAULT_MARGIN_RELATIVE) return startMargin;
if ((mMarginFlags & NEED_RESOLUTION_MASK) == NEED_RESOLUTION_MASK) {
doResolveMargins();
}
switch(mMarginFlags & LAYOUT_DIRECTION_MASK) {
case View.LAYOUT_DIRECTION_RTL:
return rightMargin;
case View.LAYOUT_DIRECTION_LTR:
default:
return leftMargin;
}
}
原来子View中startMargin的值为Integer.MIN_VALUE(负数),clone方法中,经过fillFrom()方法后,约束数据记录在Map中,其startMargin的值,与leftMargin一致,为100dp
约束解除
//ConstraintSet.java
public void clear(int viewId, int anchor) {
if (mConstraints.containsKey(viewId)) {
Constraint constraint = mConstraints.get(viewId);
if (constraint == null) {
return;
}
switch (anchor) {
case LEFT:
constraint.layout.leftToRight = Layout.UNSET;
constraint.layout.leftToLeft = Layout.UNSET;
//这里有差异
constraint.layout.leftMargin = Layout.UNSET;
constraint.layout.goneLeftMargin = Layout.UNSET_GONE_MARGIN;
break;
case START:
constraint.layout.startToEnd = Layout.UNSET;
constraint.layout.startToStart = Layout.UNSET;
//这里有差异
constraint.layout.startMargin = 0;
constraint.layout.goneStartMargin = Layout.UNSET_GONE_MARGIN;
break;
}
}
LEFT和START在移除约束时,赋值时有差异,一个是-1,一个是0
约束重新建立
//ConstraintSet.java
public void connect(int startID, int startSide, int endID, int endSide, int margin) {
switch (startSide) {
case LEFT:
if (endSide == LEFT) {
constraint.layout.leftToLeft = endID;
constraint.layout.leftToRight = Layout.UNSET;
} else if (endSide == RIGHT) {
constraint.layout.leftToRight = endID;
constraint.layout.leftToLeft = Layout.UNSET;
} else {
throw new IllegalArgumentException("Left to " + sideToString(endSide) + " undefined");
}
//重新赋值
constraint.layout.leftMargin = margin;
break;
}
}
重新约束后,leftMargin值进行重新修改。即200dp
布局更新
调用流程:applyTo(ConstraintLayout constraintLayout)
——>applyToInternal(ConstraintLayout constraintLayout, boolean applyPostLayout)}
——>刷新布局
//ConstraintSet.java
void applyToInternal(ConstraintLayout constraintLayout, boolean applyPostLayout) {
for (int i = 0; i < count; i++) {
...
Constraint constraint = mConstraints.get(id);
ConstraintLayout.LayoutParams param = (ConstraintLayout.LayoutParams) view
.getLayoutParams();
//这个方法进去
constraint.applyTo(param);
}
}
--------------
public void applyTo(ConstraintLayout.LayoutParams param) {
...
//步骤1中记录的mConstraints值,在重新建立约束后,新的布局参数(leftMargin=200dp)会更新到param中,即子View的布局参数被修改
...
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
//MarginStart由Integer.MIN_VALUE,此时赋予了实际的值,该案例的100dp.
param.setMarginStart(layout.startMargin);
param.setMarginEnd(layout.endMargin);
}
}
在该过程,原先子View的startMargin值为Integer.MIN_VALUE,经过数据覆盖,子View的startMargin有了实际的值:100dp
在后续的刷新流程中,因startMargin的优先级,高于leftMargin,当同时存在startMargin和leftMargin,leftMargin不起作用。布局中非常直观体现。
小结
单方面约束ConstraintSet.LEFT
,需要综合考虑ConstraintSet.START
的存在冲突, ConstraintSet.RIGHT
与ConstraintSet.END
类同