添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

最近一个需求需要动态添加删除 ConstraintLayout 里的元素,一时不知道如何处理。虽然 ConstraintLayout 确实减少了层级,提升了绘制效率,但对于动态增删却一直没有尝试过。借着这个需求也好好调研了下 ConstraintLayout 的一些相关属性。

按照以往的经验,增删view应该也跟 RelativityLayout 或者 LinearLayout 一样,直接添加就行了。不过在查阅了开发文档和Stack Overflow之后,发现并不是这么简单。这里有个核心的类 ConstraintSet ,控制了元素的位置。相当于原来 RelativityLayout 里的 addRule 方法,但确实另外一个类来处理这些。

需求如下:

点击 button_addtop 时添加一个textview在 image 之下 button_addtop 之上。
点击 button_addbottom 时添加一个textview在 button_addbottom 之下。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:background="@color/colorAccent">
    <ImageView
        android:id="@+id/image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/dice_1"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>
    <Button
        android:id="@+id/button_addtop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="addtop"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@id/image"/>
    <Button
        android:id="@+id/button_addbottom"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="addbottom"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@id/button_addtop"/>
</androidx.constraintlayout.widget.ConstraintLayout>
代码如下:
override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
): View? {
    val root : ConstraintLayout = inflater.inflate(R.layout.fragment_fourth, container, false) as ConstraintLayout
    root.findViewById<Button>(R.id.button_addtop).setOnClickListener(object : View.OnClickListener {
        override fun onClick(v: View?) {
            textView = TextView(context)
            textView.text = "Naruto"
            textView.id = View.generateViewId()
            textView.background = ColorDrawable(resources.getColor(R.color.colorPrimaryDark, null))
            root.addView(textView)
            val set = ConstraintSet()
            set.clone(root)
            set.connect(textView.id, ConstraintSet.TOP, R.id.image, ConstraintSet.BOTTOM, 500)
            set.connect(textView.id, ConstraintSet.LEFT, ConstraintSet.PARENT_ID, ConstraintSet.LEFT, 200)
            set.connect(textView.id, ConstraintSet.BOTTOM, R.id.button_addtop, ConstraintSet.TOP, 200)
            set.applyTo(root)
            val setaddtop = ConstraintSet()
            setaddtop.clone(root)
            setaddtop.connect(R.id.button_addtop, ConstraintSet.TOP, textView.id, ConstraintSet.BOTTOM)
            setaddtop.applyTo(root)
    root.findViewById<Button>(R.id.button_addbottom).setOnClickListener(object : View.OnClickListener {
        override fun onClick(v: View?) {
            textView = TextView(context)
            textView.text = "Kagawa"
            textView.id = View.generateViewId()
            textView.background = ColorDrawable(resources.getColor(R.color.colorPrimaryDark, null))
            root.addView(textView)
            val set = ConstraintSet()
            set.clone(root)
            set.connect(textView.id, ConstraintSet.TOP, R.id.button_addbottom, ConstraintSet.BOTTOM)
            set.applyTo(root)
    return root

ConstraintConstraintSet的内部静态类,包括75个属性,是个工具类。这些属性都是ConstraintLayout在布局时设置的xml设置项。
clone方法复制根布局的ConstraintSet值,将所有view的Constraints.LayoutParams都拷贝到ConstraintSetmConstraints对象里,这是一个HashMap

public void clone(ConstraintLayout constraintLayout) {
	// 获取当前布局的子View数量
    int count = constraintLayout.getChildCount();
    // 清空当前ConstraintSet的mConstraints
    this.mConstraints.clear();
	// 循环拷贝当前布局的子view param到mConstraints中
    for(int i = 0; i < count; ++i) {
        View view = constraintLayout.getChildAt(i);
        LayoutParams param = (LayoutParams)view.getLayoutParams();
        int id = view.getId();
        if (id == -1) {
            throw new RuntimeException("All children of ConstraintLayout must have ids to use ConstraintSet");
        if (!this.mConstraints.containsKey(id)) {
            this.mConstraints.put(id, new ConstraintSet.Constraint());
        ConstraintSet.Constraint constraint = (ConstraintSet.Constraint)this.mConstraints.get(id);
        constraint.fillFrom(id, param);
        constraint.visibility = view.getVisibility();
        if (VERSION.SDK_INT >= 17) {
            constraint.alpha = view.getAlpha();
            constraint.rotation = view.getRotation();
            constraint.rotationX = view.getRotationX();
            constraint.rotationY = view.getRotationY();
            constraint.scaleX = view.getScaleX();
            constraint.scaleY = view.getScaleY();
            float pivotX = view.getPivotX();
            float pivotY = view.getPivotY();
            if ((double)pivotX != 0.0D || (double)pivotY != 0.0D) {
                constraint.transformPivotX = pivotX;
                constraint.transformPivotY = pivotY;
            constraint.translationX = view.getTranslationX();
            constraint.translationY = view.getTranslationY();
            if (VERSION.SDK_INT >= 21) {
                constraint.translationZ = view.getTranslationZ();
                if (constraint.applyElevation) {
                    constraint.elevation = view.getElevation();
        if (view instanceof Barrier) {
            Barrier barrier = (Barrier)view;
            constraint.mBarrierAllowsGoneWidgets = barrier.allowsGoneWidget();
            constraint.mReferenceIds = barrier.getReferencedIds();
            constraint.mBarrierDirection = barrier.getType();

connect方法则根据不同的属性对应的整型值将需要修改的属性进行赋值。
public void connect(int startID, int startSide, int endID, int endSide)
public void connect(int startID, int startSide, int endID, int endSide, int margin)
以带margin的方法为例:

public void connect(int startID, int startSide, int endID, int endSide, int margin) { // 查找View.getId对应的限制是否存在,不存在则创建一个value if (!mConstraints.containsKey(startID)) { mConstraints.put(startID, new Constraint()); // 找到对应的值 Constraint constraint = mConstraints.get(startID); switch (startSide) { case LEFT: // 设置LEFT侧属性,endID是对应的约束布局对象 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"); // 设置距left依赖的margin constraint.layout.leftMargin = margin; break; case RIGHT: // 设置RIGHT侧属性,endID是对应的约束布局对象 if (endSide == LEFT) { constraint.layout.rightToLeft = endID; constraint.layout.rightToRight = Layout.UNSET; } else if (endSide == RIGHT) { constraint.layout.rightToRight = endID; constraint.layout.rightToLeft = Layout.UNSET; } else { throw new IllegalArgumentException("right to " + sideToString(endSide) + " undefined"); // 设置距right依赖的margin constraint.layout.rightMargin = margin; break; case TOP: // 设置TOP侧属性,endID是对应的约束布局对象 if (endSide == TOP) { constraint.layout.topToTop = endID; constraint.layout.topToBottom = Layout.UNSET; constraint.layout.baselineToBaseline = Layout.UNSET; } else if (endSide == BOTTOM) { constraint.layout.topToBottom = endID; constraint.layout.topToTop = Layout.UNSET; constraint.layout.baselineToBaseline = Layout.UNSET; } else { throw new IllegalArgumentException("right to " + sideToString(endSide) + " undefined"); // 设置距top依赖的margin constraint.layout.topMargin = margin; break; case BOTTOM: // 设置BOTTOM侧属性,endID是对应的约束布局对象 if (endSide == BOTTOM) { constraint.layout.bottomToBottom = endID; constraint.layout.bottomToTop = Layout.UNSET; constraint.layout.baselineToBaseline = Layout.UNSET; } else if (endSide == TOP) { constraint.layout.bottomToTop = endID; constraint.layout.bottomToBottom = Layout.UNSET; constraint.layout.baselineToBaseline = Layout.UNSET; } else { throw new IllegalArgumentException("right to " + sideToString(endSide) + " undefined"); // 设置距bottom依赖的margin constraint.layout.bottomMargin = margin; break; case BASELINE: // 设置BASELINE属性,endID是对应的约束布局对象 if (endSide == BASELINE) { constraint.layout.baselineToBaseline = endID; constraint.layout.bottomToBottom = Layout.UNSET; constraint.layout.bottomToTop = Layout.UNSET; constraint.layout.topToTop = Layout.UNSET; constraint.layout.topToBottom = Layout.UNSET; } else { throw new IllegalArgumentException("right to " + sideToString(endSide) + " undefined"); break; case START: // 设置START侧属性,endID是对应的约束布局对象 if (endSide == START) { constraint.layout.startToStart = endID; constraint.layout.startToEnd = Layout.UNSET; } else if (endSide == END) { constraint.layout.startToEnd = endID; constraint.layout.startToStart = Layout.UNSET; } else { throw new IllegalArgumentException("right to " + sideToString(endSide) + " undefined"); // 设置距start依赖的margin constraint.layout.startMargin = margin; break; case END: // 设置END侧属性,endID是对应的约束布局对象 if (endSide == END) { constraint.layout.endToEnd = endID; constraint.layout.endToStart = Layout.UNSET; } else if (endSide == START) { constraint.layout.endToStart = endID; constraint.layout.endToEnd = Layout.UNSET; } else { throw new IllegalArgumentException("right to " + sideToString(endSide) + " undefined"); // 设置距end依赖的margin constraint.layout.endMargin = margin; break; default: throw new IllegalArgumentException( sideToString(startSide) + " to " + sideToString(endSide) + " unknown");

applyto方法将属性设置给布局

public void applyTo(ConstraintLayout constraintLayout) {
    this.applyToInternal(constraintLayout);
    constraintLayout.setConstraintSet((ConstraintSet)null);

实际干活的是applyToInternal

void applyToInternal(ConstraintLayout constraintLayout, boolean applyPostLayout) {
    int count = constraintLayout.getChildCount();
    HashSet<Integer> used = new HashSet<Integer>(mConstraints.keySet());
    for (int i = 0; i < count; i++) {
        View view = constraintLayout.getChildAt(i);
        int id = view.getId();
        if (!mConstraints.containsKey(id)) {
            Log.w(TAG, "id unknown " + Debug.getName(view));
            continue;
        if (mForceId && id == -1) {
            throw new RuntimeException("All children of ConstraintLayout must have ids to use ConstraintSet");
        if (id == -1) {
            continue;
        if (mConstraints.containsKey(id)) {
            used.remove(id);
            Constraint constraint = mConstraints.get(id);
            if (view instanceof Barrier) {
                constraint.layout.mHelperType = BARRIER_TYPE;
            if (constraint.layout.mHelperType != UNSET) {
                switch (constraint.layout.mHelperType) {
                    case BARRIER_TYPE:
                        Barrier barrier = (Barrier) view;
                        barrier.setId(id);
                        barrier.setType(constraint.layout.mBarrierDirection);
                        barrier.setMargin(constraint.layout.mBarrierMargin);
                        barrier.setAllowsGoneWidget(constraint.layout.mBarrierAllowsGoneWidgets);
                        if (constraint.layout.mReferenceIds != null) {
                            barrier.setReferencedIds(constraint.layout.mReferenceIds);
                        } else if (constraint.layout.mReferenceIdString != null) {
                            constraint.layout.mReferenceIds = convertReferenceString(barrier,
                                    constraint.layout.mReferenceIdString);
                            barrier.setReferencedIds(constraint.layout.mReferenceIds);
                        break;
            ConstraintLayout.LayoutParams param = (ConstraintLayout.LayoutParams) view
                    .getLayoutParams();
            param.validate();
            constraint.applyTo(param);
            if (applyPostLayout) {
                ConstraintAttribute.setAttributes(view, constraint.mCustomConstraints);
            view.setLayoutParams(param);
            if (constraint.propertySet.mVisibilityMode == VISIBILITY_MODE_NORMAL) {
                view.setVisibility(constraint.propertySet.visibility);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                view.setAlpha(constraint.propertySet.alpha);
                view.setRotation(constraint.transform.rotation);
                view.setRotationX(constraint.transform.rotationX);
                view.setRotationY(constraint.transform.rotationY);
                view.setScaleX(constraint.transform.scaleX);
                view.setScaleY(constraint.transform.scaleY);
                if (constraint.transform.transformPivotTarget != UNSET) {
                    View layout = (View) view.getParent();
                    View center = layout.findViewById(constraint.transform.transformPivotTarget);
                    if (center != null) {
                        float cy = (center.getTop() + center.getBottom()) / 2.0f;
                        float cx = (center.getLeft() + center.getRight()) / 2.0f;
                        if (view.getRight() - view.getLeft() > 0 && view.getBottom() - view.getTop() > 0) {
                            float px = (cx - view.getLeft());
                            float py = (cy - view.getTop());
                            view.setPivotX(px);
                            view.setPivotY(py);
                } else {
                    if (!Float.isNaN(constraint.transform.transformPivotX)) {
                        view.setPivotX(constraint.transform.transformPivotX);
                    if (!Float.isNaN(constraint.transform.transformPivotY)) {
                        view.setPivotY(constraint.transform.transformPivotY);
                view.setTranslationX(constraint.transform.translationX);
                view.setTranslationY(constraint.transform.translationY);
                if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
                    view.setTranslationZ(constraint.transform.translationZ);
                    if (constraint.transform.applyElevation) {
                        view.setElevation(constraint.transform.elevation);
        } else {
            Log.v(TAG, "WARNING NO CONSTRAINTS for view " + id);
    for (Integer id : used) {
        Constraint constraint = mConstraints.get(id);
        if (constraint.layout.mHelperType != UNSET) {
            switch (constraint.layout.mHelperType) {
                case BARRIER_TYPE:
                    Barrier barrier = new Barrier(constraintLayout.getContext());
                    barrier.setId(id);
                    if (constraint.layout.mReferenceIds != null) {
                        barrier.setReferencedIds(constraint.layout.mReferenceIds);
                    } else if (constraint.layout.mReferenceIdString != null) {
                        constraint.layout.mReferenceIds = convertReferenceString(barrier,
                                constraint.layout.mReferenceIdString);
                        barrier.setReferencedIds(constraint.layout.mReferenceIds);
                    barrier.setType(constraint.layout.mBarrierDirection);
                    barrier.setMargin(constraint.layout.mBarrierMargin);
                    ConstraintLayout.LayoutParams param = constraintLayout
                            .generateDefaultLayoutParams();
                    barrier.validateParams();
                    constraint.applyTo(param);
                    constraintLayout.addView(barrier, param);
                    break;
        if (constraint.layout.mIsGuideline) {
            Guideline g = new Guideline(constraintLayout.getContext());
            g.setId(id);
            ConstraintLayout.LayoutParams param = constraintLayout.generateDefaultLayoutParams();
            constraint.applyTo(param);
            constraintLayout.addView(g, param);

实际上是把所有子view的param再赋值给对应的view,并刷新布局。

https://www.zoftino.com/adding-views-&-constraints-to-android-constraint-layout-programmatically

https://stackoverflow.com/questions/45263159/constraintlayout-change-constraints-programmatically

背景最近一个需求需要动态添加删除ConstraintLayout里的元素,一时不知道如何处理。虽然ConstraintLayout确实减少了层级,提升了绘制效率,但对于动态增删却一直没有尝试过。借着这个需求也好好调研了下ConstraintLayout的一些相关属性。按照以往的经验,增删view应该也跟RelativityLayout或者LinearLayout一样,直接添加就行了。不过在查阅了开发文档和Stack Overflow之后,发现并不是这么简单。这里有个核心的类ConstraintSet,控
动态设置ConstraintLayout约束 以前一直是在布局里直接设置ConstraintLayout的约束,前几天遇到了一个有点特殊的需求,需要动态设置布局,xml布局使用的ConstraintLayout,所以就立马恶补动态设置ConstraintLayout约束的知识,总结如下。 获取要设置目标控件的父布局。 val constraintLayoutRoot = getView<ConstraintLayout>(R.id.constraint_dynamics_root)
* 使用场景:设置布局的时候,想通过ConstraintLayout实现滑块与文字同步移动 * 思路:ConstraintLayout在xml布局中有layout_constraintvertical_bias设置项能否找到一个功能类来完成? * ConstraintLaout控件引入后可以看到 constraintLayout.setConstraintSet(ConstraintSet set); * 关键操作是:ConstraintSet constraintSet.setVerticalBias(.
如果父View是ConstraintLayout约束布局,当修改某个view时候,直接创建ConstraintLayout.LayoutParams会有问题,没办法直接满足需求。 所有使用ConstraintSet绝对没错 具体使用步骤 ConstraintSet set= new ConstraintSet(); //获取一新的ConstraintLayout //mConstra...
RelativeLayout.Layoutparams params = (RelativeLayout.LayoutParams)view1.getLayoutParams(); params.addRule(RelativeLayout.LEFT_OF...
一、使用方式: Android studio 2.2以上 并添加依赖 compile ‘com.android.support.constraint:constraint-layout:1.0.1’ 本人用的AS3.0 Pre,不需要添加依赖库,Kotlin 默认就是ConstraintLayout布局.(感觉这东西有点像Masonry啊,写习惯了Masonry 这个也很溜
ConstrainLayout是一款布局View,再Design库中,现已被大家广泛接受并使用。ConstrainLayout的布局采用的方式和其他都不同,他的对其方式是类似RelativeLayout,但是和RelativeLayout有明显的区别。 在布局渲染的时候,ConstrainLayout的子View是通过在一个容器中找到自己的位置,通过位置和对其方式来固定,所以在布局优化中,尝尝被提起到。
FrameLayout.LayoutParams lytp = new FrameLayout.LayoutParams(80,LayoutParams.WRAP_CONTENT); lytp .gravity = Gravity.CENTER; btn.setLayoutParams(lytp); Relat... Barrier 也是辅助线,onDraw 和onMearsure方法也没有实现。但是和Gudline 不同,他是一条可以变化的线 Gudline 有start end 或者percent 来固定位置 Barrier 这条辅助线就很有意思啦。 他可以让你的布局充满动态变化 拿一个场景举例 平时我们如果做登录。多语言的时候 android:layout_width="match_parent" android:layout_height="160dp" android:background="@drawable/fullscreen_bg_gradient">
Android基础之布局ConstraintLayout Google I/O 2016 上发布了 ConstraintLayout,据说很强大,那就一探究竟吧! gradle配置