ConstraintLayout代码中LeftToLeft&RightToRight约束失效解决

背景交代

布局中,约束左、顶方向,左相对100dp,顶相对100dp。
代码中想要修改到:左相对200dp。
处理不对可能会失效,布局和逻辑列出供查看。

环境:constraintlayout版本2.1.1
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'

简单原因放在后面。
Demo布局

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.RIGHTConstraintSet.END类同

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值