ConstraintLayout ,ConstraintHelper

其实这东西刚出来的时候玩了会感觉太累,就放弃了。
不过看这个google不会放弃这东西,咱还是在平时的demo练习中用这个来写吧,写多了估计就习惯了。
下边就记录下平时使用的问题
看这里https://blog.csdn.net/fallfollowernolisten/article/details/61195236
2020年8月,发现这个东西更新到2.0的release版本了

dependencies {
    implementation 'androidx.constraintlayout:constraintlayout:2.0.0'
}

这里是2.0版的测试记录,后续版本修改了许多东西,刚开始学的话最好看下新的版本都添加修改了啥。

首先基本的属性

1. Relative positioning

They all take a reference id to another widget, or the parent (which will reference the parent container, i.e. the ConstraintLayout):

layout_constraintLeft_toLeftOf
layout_constraintLeft_toRightOf
layout_constraintRight_toLeftOf
layout_constraintRight_toRightOf
layout_constraintTop_toTopOf
layout_constraintTop_toBottomOf
layout_constraintBottom_toTopOf
layout_constraintBottom_toBottomOf
layout_constraintBaseline_toBaselineOf
layout_constraintStart_toEndOf
layout_constraintStart_toStartOf
layout_constraintEnd_toStartOf
layout_constraintEnd_toEndOf

简单说下
app:layout_constraintLeft_toLeftOf
约束控件的左边界,和某个控件的左边界对齐
app:layout_constraintLeft_toRightOf
约束控件的左边界,和某个控件的右边界对齐

很多时候后边跟着的就是个parent,那么这个parent是谁,就是ConstraintLayout这个整体布局
举例


image.png
<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="?attr/selectableItemBackground"
    android:clickable="true"
    android:padding="9dp">
      <ImageView
        android:id="@+id/iv_cover"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:scaleType="centerCrop"
        android:src="@mipmap/ic_launcher"
        android:layout_marginRight="10dp"
        app:layout_constraintDimensionRatio="5:10"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/guideline"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:textColor="#000"
        app:layout_constraintLeft_toRightOf="@+id/iv_cover"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="title........" />

看下imageview里的
app:layout_constraintLeft_toLeftOf="parent" 就是和ConstraintLayout的左边界对齐
然后看下textview里的
app:layout_constraintLeft_toRightOf="@+id/guideline" 就是说textview的左边界在imageview的右边

layout_constraintBaseline_toBaselineOf 这个就是文字的基线对齐的意思


image.png

我们用这个布局的时候需要了解,这玩意就是靠一圈4个橡皮筋拉着的。
所以宽高的wrap_content,0dp是和其他有点区别的
0dp才相当与match_parent,而这个布局里的match_parent是不建议使用的
如果你用了match_parent,那么这个控件的约束就没了意义了,比如你在a的左边,在b的右边,最后你会发现他的宽度和ConstraintLayout一样,也就是左右两边的橡皮筋失去了意义。

android:layout_width="0dp"
android:layout_height="wrap_content"
如果我们只设置了 layout_constraintLeft_ ,layout_constraintTop_ ,就相当与左边和上边有橡皮筋,那么这时候控件就在左上角这个位置。比如宽度是个wrap或者固定的大小。。这时候如果你设置一个layout_constraintRight,那么相当与右边也有一个橡皮筋了。结果就是这个控件跑到中间去了。这个时候如果把宽度设置为0dp,那么控件宽度就铺满全屏了

这些基础属性其实也就和相对布局差不多,那么它的优势咋体现的?所以肯定不止这些了。

常用的两种基准点

1. Guideline

宽高属性是无效的,因为这个并不画到屏幕上

public class Guideline extends View {
    public Guideline(Context context) {
        super(context);
        super.setVisibility(8);//不可见
    }
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        this.setMeasuredDimension(0, 0);//宽高是0
    }

//有如下3个方法可以设置位置,不过只有一个生效
    public void setGuidelineBegin(int margin) {
        LayoutParams params = (LayoutParams)this.getLayoutParams();
        params.guideBegin = margin;
        this.setLayoutParams(params);
    }

    public void setGuidelineEnd(int margin) {
        LayoutParams params = (LayoutParams)this.getLayoutParams();
        params.guideEnd = margin;
        this.setLayoutParams(params);
    }

    public void setGuidelinePercent(float ratio) {
        LayoutParams params = (LayoutParams)this.getLayoutParams();
        params.guidePercent = ratio;
        this.setLayoutParams(params);
    }

需要注意的是这个控件需要一个orientation,来决定这条线是横的还是竖的

    <androidx.constraintlayout.widget.Guideline
android:orientation="horizontal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
image.png
layout_constraintGuide_percent 三选一,优先级最高,百分比来设置位置
layout_constraintGuide_begin 优先级第二,水平线的话就是上下,垂直线就是左右距离
layout_constraintGuide_end 优先级最低
orientation 线的方向
<androidx.constraintlayout.widget.Guideline
    android:id="@+id/guideline"
    android:orientation="vertical"
    app:layout_constraintGuide_percent="0.5"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

一般就弄2个属性,方向和位置,

2. Barrier

public class Barrier extends ConstraintHelper{
    public Barrier(Context context) {
        super(context);
        super.setVisibility(8);//默认是不可见的GONE
    }
//支持的属性
            for(int i = 0; i < N; ++i) {
                int attr = a.getIndex(i);
                if (attr == styleable.ConstraintLayout_Layout_barrierDirection) {
                    this.setType(a.getInt(attr, 0));
                } else if (attr == styleable.ConstraintLayout_Layout_barrierAllowsGoneWidgets) {
                    this.mBarrier.setAllowsGoneWidget(a.getBoolean(attr, true));
                } else if (attr == styleable.ConstraintLayout_Layout_barrierMargin) {
                    int margin = a.getDimensionPixelSize(attr, 0);
                    this.mBarrier.setMargin(margin);
                }
            }
}
attribute introduction
app:barrierDirection 约束的方向,在app:constraint_referenced_ids所指定的这些控件的哪边
constraint_referenced_ids 约束的id
barrierMargin 就是加个间距
app:barrierAllowsGoneWidgets 后边有举例说明,就是关联的控件不可见以后,margin是否生效

使用场景:比如textview1和textview2的右边有个textview3, 3需要在1和2个右边,而1和2的宽度谁大不确定。


image.png
<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/tv1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="testssssss"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="80dp"
        android:text="test"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="test3"
        app:layout_constraintStart_toEndOf="@+id/barrierleft"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.constraintlayout.widget.Barrier
        android:id="@+id/barrierleft"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:barrierDirection="end"
        app:barrierMargin="10dp"
        app:constraint_referenced_ids="tv1,tv2" />
</androidx.constraintlayout.widget.ConstraintLayout>

app:barrierAllowsGoneWidgets


image.png

app:barrierAllowsGoneWidgets:默认是true的,
举例,referenced的那2个控件都设置为gone,那么这个Barrier在哪里?为true的话,就在容器顶部,为false的话,那个margin50dp还是生效的

    <androidx.constraintlayout.widget.Barrier
        android:id="@+id/barrier"
        app:barrierDirection="bottom"
        app:barrierMargin="50dp"
        app:barrierAllowsGoneWidgets="true"
        app:constraint_referenced_ids="button8,textView4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

margin属性

最开始不支持负的的,目前已经支持负的,至少2.1.3版是支持负的的

所以这里的记录参考下即可,这里的代码时间有点久了,有些已经不正确了。

Gone

ConstraintLayout里的控件如果可见性为GONE的时候,和其他容器是有区别的。
控件的约束还是生效的,只不过其他属性,比如margin啥的都失效了,自身大小也是按照0来算的.
主要分两种情况
单边约束:只有左或者右,只有上或者下
双边约束:左右都有约束,或者上下都有约束

比如控件A,双边约束,不可见的时候它的位置在ConstraintLayout的正中心,大小为0.如果此时有别的控件相对它的位置,也是相对正中心的.

 android:visibility="gone"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"

比如,没有bottom属性,他么gone的时候它在ConstraintLayout最上方居中的位置,大小为0

android:visibility="gone"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"

比如,下边这种单边约束,那么gone的时候在右上角,大小为0,根据它的约束条件,可能在左上角,左下角,右下角.

        android:visibility="gone"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"

goneMargin属性

只有在这个控件相对的约束的那个控件不可见的时候,下边的属性才会起作用。
比如控件B在控件A的右边,当A不可见的 时候,goneMarginLeft 就会起作用了。换句话说,他参考的那个边界的控件为gone的时候,对应的goneMargin会起作用.
比如下边的在A可见的时候margin是20,不可见的时候margin是10.

        android:layout_marginStart="20dp"
        app:layout_goneMarginStart="10dp"

– layout_goneMarginBottom

– layout_goneMarginEnd

– layout_goneMarginLeft

– layout_goneMarginRight

– layout_goneMarginStart

– layout_goneMarginTop

bias偏差来约束

 <TextView
        android:id="@+id/tv7"
        android:text="text7"
        android:visibility="visible"
        app:layout_constraintHorizontal_bias="0.7"
        app:layout_constraintVertical_bias="0.7"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tv6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

效果图如下,需要注意的是
app:layout_constraintHorizontal_bias="0.7"
app:layout_constraintVertical_bias="0.7"
这个生效的前提是两边都有了约束,而且是wrap_content,如果没有上边的2个属性,那么默认的是居中显示的。上边的2个属性就是用来改变弹簧拉伸的距离的。


image.png

layout_constraintDimensionRatio

控件的宽高比列的约束,这个以前碰到比如我们的封面图宽高比是固定的,可手机的宽是不一样的,所以都还得回来算下高度。。现在就简单了
分析1,如下这种,是没有效果的,宽高里边必须有一个是0dp【或者说是确定的宽或者高】,才可以参照其中一个不为0的来计算。
当然也可以2个都是0dp的,这种情况就相当与宽高都是match_parent的,会根据宽高比例来计算

app:layout_constraintDimensionRatio="1:1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"

比如这样写layout_constraintDimensionRatio 也可以是个小数

app:layout_constraintDimensionRatio="2"
        android:layout_width="0dp"
        android:layout_height="wrap_content" 

小数 2或者1:1这种默认的都宽/高的比例
还有另外的写法 “w,4:1” 或者“h,4” ,其实上边的 两种写法就相当于省略是w逗号,因为默认就是宽高比,
加上h逗号以后就成了高和宽的比了。

链条布局(Chains)

如图,简单的创建方法,添加自己打算链条布局的控件,完事鼠标滑动全选,右键,如下图,选择chain,垂直的或者水平的,就可以自动生成相关的代码了


image.png

下边分析下他的属性。需要注意下,链条的特性都是在第一个view上设置的


image.png

上图可以看到chainStyle 有3种,默认的就是
1.spread,铺开的,如下图,两边中间一样的间隔


image.png

2.spread_inside 两边没有间隔,就中间的有,如下图


image.png

3.packed,就是大家相当于一个整体,居中显示了。


image.png

最后,这些效果其实只是在wrap_content,或者固定的宽度才有效果,如果有一个为0dp,那么它就会铺满剩余的空间的, 如果有2个 都是0dp咋办,那么就这2个平分剩下的空间。 上边的chain_style其实也就失去了意义了。

当然了这里也可以设置layout_constraintHorizontal_weight ,比重,和线性布局一样的道理。

circle相关属性

layout_constraintCircle references another widget id
layout_constraintCircleRadius the distance to the other widget center
layout_constraintCircleAngle which angle the widget should be at (in degrees, from 0 to 360)
image.png
app:layout_constraintCircle="@id/btn_pattern"
        app:layout_constraintCircleRadius="20dp"
        app:layout_constraintCircleAngle="0"

效果图如下,可以看到角度是0的话默认是在正上方的,改为90就跑到右边去了,所以应该是顺时针了。
而且能看出距离是以控件的中心点来比较的。


image.png

Group

看下源码,作用很简单,就是把referenced_ids关联的那些控件统一设置可见性以及elevation

public class Group extends ConstraintHelper {
    public void setVisibility(int visibility) {
        super.setVisibility(visibility);
        this.applyLayoutFeatures();
    }

    public void setElevation(float elevation) {
        super.setElevation(elevation);
        this.applyLayoutFeatures();
    }
}

xml里使用

    <androidx.constraintlayout.widget.Group
        android:id="@+id/group_divider"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="visible"
        android:elevation="20dp"
        app:constraint_referenced_ids="tv1,tv2" />

还有个和Group几乎一样的类,不过是抽象的,它有个实现类Flow

public abstract class VirtualLayout extends ConstraintHelper 

Flow

就是把关联id的那些view统一处理,类型流式布局,关联id的布局属性失效了,Flow里的展示顺序就是按照referenced_ids里的顺序显示的

public class Flow extends VirtualLayout {

demo,如果不加Flow,默认其他两个控件,一个在右上角,一个在右下角的,结果加了以后,默认水平方向的

    <androidx.constraintlayout.utils.widget.ImageFilterButton
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:id="@+id/ifb"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:srcCompat="@drawable/ic_avoid_ferries"
        app:altSrc="@drawable/ic_addnextpoint_d"/>
    <androidx.constraintlayout.utils.widget.ImageFilterView
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:id="@+id/ifv"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:srcCompat="@drawable/ic_avoid_ferries"
        app:altSrc="@drawable/ic_addnextpoint_d"/>
    <androidx.constraintlayout.helper.widget.Flow
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/flow1"
        app:constraint_referenced_ids="ifb,ifv"
        android:orientation="horizontal"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent">

image1.png

image2.png

aligned.png

下边的介绍都是以horizontal来介绍的,vertical一样的道理.

参数 介绍
orientation 方向,水平或者垂直
padding 间隔
flow_wrapMode aligned:每行的中心线对齐,每一列的中心线对齐,如上图aligned.png;chain:就是按照流式布局,每行能放几个就放几个,放不下的换行,然后每行都按照chain来处理,至于chain的style,就是下边的flow_horizontalStyle和flow_verticalStyle来决定了,默认是spread;none:空间够的话就类似spread,不够的话就是大家挤成一行居中显示,显示不下的就显示不下,只有中间的能显示,跑到屏幕外的就看不到了
下边这几个style只有在上边的wrapMode是chain的时候才有效
flow_horizontalStyle 这个和chain的style差不多有packed,spread,spread_inside
flow_firstHorizontalStyle 上边那个是整体设置每行的,这个是单独设置第一行的
flow_lastHorizontalStyle 同上,这个是单独设置最后一行的style的
flow_verticalStyle 和水平的一样,这个是设置垂直方向的控件分布的,也是那3种
flow_firstVerticalBias 同理
flow_lastVerticalBias 同理
flow_maxElementsWrap 一行最多显示几个控件
flow_horizontalGap 间隔距离
horizontalAlign 对齐方式
horizontalBias 没玩明白

Layer

public class Layer extends ConstraintHelper

乍一看和Group,VirtualLayout差不多啊,都是可以统一设置关联id对应的view的可见性,elevation的,可仔细看看还是不太一样的,
这个Layer是可以在页面上显示的,它的大小范围就是包裹所有reference id控件的范围,
好处是 不用增加view的层级,xml里可以看到它和其他reference ids 都是同级的
另外,只有visible以及elevation是在xml就生效的,其他属性需要代码设定才能生效.
支持的属性如下,相当于对所有reference的view都设置了这个属性,这样就可以实现对Layer关联的所有view进行统一的平移,拉伸,旋转之类的动画了,非常简单就实现了

            ValueAnimator.ofFloat(0f,360f).setDuration(2222).apply {
                addUpdateListener {
                    layer.rotation= it.animatedValue as Float
                }
            }.start()

image.png

看下源码里咋设置的,注意下,它的默认值好多都是无穷大的,所以
必须设置rotation的值,如果不需要旋转,那么设置0即可
如果不设置rotation,你会发现scale或者translate以后,控件不见了,因为值都成了NaN了,估计跑到屏幕外边去了,
另外,默认的pivot x和y是layer的中心

    private float mRotationCenterX = 0.0F / 0.0;
    private float mRotationCenterY = 0.0F / 0.0;
    private float mGroupRotateAngle = 0.0F / 0.0;
    ConstraintLayout mContainer;
    private float mScaleX = 1.0F;
    private float mScaleY = 1.0F;
    protected float mComputedCenterX = 0.0F / 0.0;
    protected float mComputedCenterY = 0.0F / 0.0;
    protected float mComputedMaxX = 0.0F / 0.0;
    protected float mComputedMaxY = 0.0F / 0.0;
    protected float mComputedMinX = 0.0F / 0.0;
    protected float mComputedMinY = 0.0F / 0.0;
    private void transform() {
        if (this.mContainer != null) {
            if (this.mViews == null) {
                this.reCacheViews();
            }

            this.calcCenters();
            double rad = Math.toRadians((double)this.mGroupRotateAngle);
            float sin = (float)Math.sin(rad);
            float cos = (float)Math.cos(rad);
            float m11 = this.mScaleX * cos;
            float m12 = -this.mScaleY * sin;
            float m21 = this.mScaleX * sin;
            float m22 = this.mScaleY * cos;

            for(int i = 0; i < this.mCount; ++i) {
                View view = this.mViews[i];
                int x = (view.getLeft() + view.getRight()) / 2;
                int y = (view.getTop() + view.getBottom()) / 2;
                float dx = (float)x - this.mComputedCenterX;//mComputedCenterX/Y就是Layer的中心点
                float dy = (float)y - this.mComputedCenterY;
                float shiftx = m11 * dx + m12 * dy - dx + this.mShiftX;
                float shifty = m21 * dx + m22 * dy - dy + this.mShiftY;
                view.setTranslationX(shiftx);
                view.setTranslationY(shifty);
                view.setScaleY(this.mScaleY);
                view.setScaleX(this.mScaleX);
                view.setRotation(this.mGroupRotateAngle);
            }

        }
    }

搜了篇帖子看下
https://blog.csdn.net/weixin_34677811/article/details/90719945

UI 编辑器所使用的属性

下面几个属性是 UI 编辑器所使用的,用了辅助拖拽布局的,在实际使用过程中,可以不用关心这些属性。

layout_optimizationLevel
layout_editor_absoluteX
layout_editor_absoluteY
layout_constraintBaseline_creator
layout_constraintTop_creator
layout_constraintRight_creator
layout_constraintLeft_creator
layout_constraintBottom_creator

ImageFilterButton,ImageFilterView

就是可以设置两张图片,然后通过设置色相饱和度等值使得一个可见一个不可见


image.png
        if (attrs != null) {
            TypedArray a = this.getContext().obtainStyledAttributes(attrs, styleable.ImageFilterView);
            int N = a.getIndexCount();
            Drawable drawable = a.getDrawable(styleable.ImageFilterView_altSrc);

            for(int i = 0; i < N; ++i) {
                int attr = a.getIndex(i);
                if (attr == styleable.ImageFilterView_crossfade) {
                    this.mCrossfade = a.getFloat(attr, 0.0F);
                } else if (attr == styleable.ImageFilterView_warmth) {
                    this.setWarmth(a.getFloat(attr, 0.0F));
                } else if (attr == styleable.ImageFilterView_saturation) {
                    this.setSaturation(a.getFloat(attr, 0.0F));
                } else if (attr == styleable.ImageFilterView_contrast) {
                    this.setContrast(a.getFloat(attr, 0.0F));
                } else if (attr == styleable.ImageFilterView_round) {
                    this.setRound(a.getDimension(attr, 0.0F));
                } else if (attr == styleable.ImageFilterView_roundPercent) {
                    this.setRoundPercent(a.getFloat(attr, 0.0F));
                } else if (attr == styleable.ImageFilterView_overlay) {
                    this.setOverlay(a.getBoolean(attr, this.mOverlay));
                }
            }

            a.recycle();
            if (drawable != null) {
                this.mLayers = new Drawable[2];
                this.mLayers[0] = this.getDrawable();
                this.mLayers[1] = drawable;//src默认的图片在底层
                this.mLayer = new LayerDrawable(this.mLayers);//上层是我们设置的altSrc图片
                this.mLayer.getDrawable(1).setAlpha((int)(255.0F * this.mCrossfade));//修改生成图片的透明度使其可见,会挡住下层的图片
                super.setImageDrawable(this.mLayer);
            }
        }

一些知识点

  1. app:layout_constrainedWidth
    这个用来约束layout_width是wrap的控件的,使其宽度不能超过两侧的约束控件,
    app:layout_constrainedHeight="true" 同理,约束上下范围的
    一个textview,左右两边有约束,大小是wrap,在文字太长的时候,效果可能不是自己要的,文字跑到两边的约束外边去了
    比如,中间的textview文字太长的话,可以看到盖住两边的约束控件了,明显不是我们要的结果
    这时候只需要简单的加个属性就ok了
app:layout_constrainedWidth="true"
image.png
<?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:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/button9"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        android:textSize="30sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/button8"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        android:textSize="30sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView"
        app:layout_constraintEnd_toStartOf="@+id/button9"
        app:layout_constraintStart_toEndOf="@+id/button8"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyy" />
</androidx.constraintlayout.widget.ConstraintLayout>

看到这里就继续把几个相关的属性一起研究下

        android:layout_width="wrap_content"
        app:layout_constrainedWidth="true"//这个是关键,设置了这个宽度才会做约束,否则下边那些都无效的
        app:layout_constraintWidth_default="percent"
        app:layout_constraintWidth_percent="0.3"
        app:layout_constraintWidth_max="150dp"
        app:layout_constraintWidth_min="50dp"

最大,最小值就是字面一起,当然了,是在view的宽是wrap或者0dp的情况下才有效,如果设置了下边两行,那么最大最小的值就无效了,因为此时相当于设置了一个固定的宽度

app:layout_constraintWidth_default="percent"
        app:layout_constraintWidth_percent="0.3"//如果不设置这个值默认是1,

layout_constraintWidth_default://有3种,
wrap【过时不用,相当于spread】,spread 就是默认的,这时候的最大,最小款是有效的
percent:宽度约束按照百分比来的,也就是layout_constraintWidth_percent,默认值是1,也就是铺满
总结一下:layout_height="wrap_content"的时候,view的宽到底是多少
没有设置layout_constrainedWidth="true"的话,宽就是wrap,最大可到容器的两端
有设置的话,如果layout_constraintWidth_default="percent"为percent,并且有个width_percent,那么就是这个percent的大小,没有最大最小width的话,那最大宽就是到两边的约束容器的距离

最后

弄这个得特别小心啊,一个不注意就会发现效果不是自己要的。
尤其是你以前是相对布局,你改成这个约束布局,举个简单的例子
一个textview,下边有个FramLayout,原来就设置个below textview,然后宽高都是match的。
现在你如果只设置了app:layout_constraintTop_toBottomOf="@+id/tv" 而不设置app:layout_constraintBottom_toBottomOf="parent"的话是不行的,而且高度也得改成0dp .

  1. 再比如ConstraintLayout 里有2个view ,顶部的A,B在A的下边,
    如果B设置成这样,只设置在A的下边,内容少的时候看不出来,如果B的内容非常多的话,超出了屏幕,你让它可以滚动,比如TextView设置可以滚动,可结果你发现还是有一部分看不见。那是以为,其实此时的TextView B的高度是和容器高度一样的,有一部分在屏幕外边的。
    所以wrap这个有点不靠谱,约束必须两边都有才好点.
android:layout_width="match_parent"
        android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/view_a"

悬浮在某个view下边咋弄?

top和bottom都在 那个view的bottom下就可以拉


image.png
        <Constraint
            android:id="@id/imageButton2"
            android:layout_width="64dp"
            android:layout_height="64dp"
            motion:layout_constraintBottom_toBottomOf="@+id/imageView8"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toBottomOf="@+id/imageView8" />

ConstraintHelper

一个帮助类,默认情况下宽高是0,除非你修改mUseViewMeasure 为true
里边就用到一个app:constraint_referenced_ids参数,设置关联的id,然后我们就可以拿到这些view做一些操作了.

    <com.google.androidstudio.motionlayoutexample.helpers.ScaleHelper
        android:id="@+id/helper2"
        app:constraint_referenced_ids="imageButton2,imageView9"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

简单看下源码

public abstract class ConstraintHelper extends View {
    protected int[] mIds = new int[32];//保存referenced_ids
    protected int mCount; //referenced_ids 的个数
    protected Context myContext;
    protected Helper mHelperWidget;
    protected boolean mUseViewMeasure = false;
    protected String mReferenceIds;
    private View[] mViews = null;
    protected void init(AttributeSet attrs) {
        if (attrs != null) {
            TypedArray a = this.getContext().obtainStyledAttributes(attrs, styleable.ConstraintLayout_Layout);
            int N = a.getIndexCount();

            for(int i = 0; i < N; ++i) {
                int attr = a.getIndex(i);
                if (attr == styleable.ConstraintLayout_Layout_constraint_referenced_ids) {
                    this.mReferenceIds = a.getString(attr);
                    this.setIds(this.mReferenceIds);//就是把id存到上边的mIds 数组里
                }
            }
        }

    }
    public int[] getReferencedIds() {
        return Arrays.copyOf(this.mIds, this.mCount);
    }

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (this.mUseViewMeasure) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        } else {
            this.setMeasuredDimension(0, 0);//可以看到默认宽高设置为0的
        }

    }
 public void updatePreLayout(ConstraintLayout container)//容器onMeasure的时候会走这里
//然后有一堆空的方法给你用,至于这些方法撒时候调用,那就需要去它的容器ConstraintLayout里去看了.
    public void updatePostLayout(ConstraintLayout container) {
    }

    public void updatePostMeasure(ConstraintLayout container) {
    }

    public void updatePostConstraints(ConstraintLayout constainer) {
    }

    public void updatePreDraw(ConstraintLayout container) {
    }

简单看下ConstraintLayout 里调用上边ConstraintHelper的地方

    public void onViewAdded(View view) {
//...
//判断是Helper类,就放到集合mConstraintHelpers里
        if (view instanceof ConstraintHelper) {
            ConstraintHelper helper = (ConstraintHelper)view;
            helper.validateParams();
            ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams)view.getLayoutParams();
            layoutParams.isHelper = true;
            if (!this.mConstraintHelpers.contains(helper)) {
                this.mConstraintHelpers.add(helper);
            }
        }

        this.mChildrenByIds.put(view.getId(), view);
        this.mDirtyHierarchy = true;
    }

    public void onViewRemoved(View view) {
//...
//移除
        this.mConstraintHelpers.remove(view);
        this.mDirtyHierarchy = true;
    }

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//...
        if (this.mDirtyHierarchy) {
            this.mDirtyHierarchy = false;
            if (this.updateHierarchy()) {//走这里
                this.mLayoutWidget.updateHierarchy();
            }
        }
}

    private boolean updateHierarchy() {
        int count = this.getChildCount();
        boolean recompute = false;

        for(int i = 0; i < count; ++i) {
            View child = this.getChildAt(i);
            if (child.isLayoutRequested()) {
                recompute = true;
                break;
            }
        }

        if (recompute) {
            this.setChildrenConstraints();//走这里
        }

        return recompute;
    }

private void setChildrenConstraints() {
//...
        helperCount = this.mConstraintHelpers.size();
        int i;
        if (helperCount > 0) {
            for(i = 0; i < helperCount; ++i) {
                ConstraintHelper helper = (ConstraintHelper)this.mConstraintHelpers.get(i);
                helper.updatePreLayout(this);// 在onMeasure的时候会调用这个
            }
        }
//...
}

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
//...
        helperCount = this.mConstraintHelpers.size();
        if (helperCount > 0) {
            for(int i = 0; i < helperCount; ++i) {
                ConstraintHelper helper = (ConstraintHelper)this.mConstraintHelpers.get(i);
                helper.updatePostLayout(this);// 调用这个方法
            }
        }
}

    protected void dispatchDraw(Canvas canvas) {
        int count;
        if (this.mConstraintHelpers != null) {
            count = this.mConstraintHelpers.size();
            if (count > 0) {
                for(int i = 0; i < count; ++i) {
                    ConstraintHelper helper = (ConstraintHelper)this.mConstraintHelpers.get(i);
                    helper.updatePreDraw(this);//又找到一个方法
                }
            }
        }

        super.dispatchDraw(canvas);
//...
}

        public final void didMeasures() {
//... 最后这个方法不知道哪里调用的..
            helperCount = this.layout.mConstraintHelpers.size();
            if (helperCount > 0) {
                for(int i = 0; i < helperCount; ++i) {
                    ConstraintHelper helper = (ConstraintHelper)this.layout.mConstraintHelpers.get(i);
                    helper.updatePostMeasure(this.layout);
                }
            }

        }

使用案例,在进去的时候对其中2个view进行scale动画

    <com.google.androidstudio.motionlayoutexample.helpers.ScaleHelper
        android:id="@+id/helper2"
        app:constraint_referenced_ids="imageButton2,imageView9"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

class ScaleHelper:ConstraintHelper{
    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    protected  var mContainer: ConstraintLayout?=null
    override fun updatePreLayout(container: ConstraintLayout) {
        super.updatePreLayout(container)
        if(container!=mContainer){
            val views=getViews(container)
            (0 until mCount).forEach {
                val view=views[it]
                val animator=ObjectAnimator.ofFloat(view,"ScaleX",0f,1f).setDuration(1000).apply {
                    interpolator = BounceInterpolator()
                }.start()
                val animator2=ObjectAnimator.ofFloat(view,"ScaleY",0f,1f).setDuration(1000).apply {
                    interpolator = BounceInterpolator()
                }.start()
//                animator.start()
            }
            mContainer=container
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容