其实这东西刚出来的时候玩了会感觉太累,就放弃了。
不过看这个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这个整体布局
举例
<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 这个就是文字的基线对齐的意思
我们用这个布局的时候需要了解,这玩意就是靠一圈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"/>
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的宽度谁大不确定。
<?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
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个属性就是用来改变弹簧拉伸的距离的。
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,垂直的或者水平的,就可以自动生成相关的代码了
下边分析下他的属性。需要注意下,链条的特性都是在第一个view上设置的
上图可以看到chainStyle 有3种,默认的就是
1.spread,铺开的,如下图,两边中间一样的间隔
2.spread_inside 两边没有间隔,就中间的有,如下图
3.packed,就是大家相当于一个整体,居中显示了。
最后,这些效果其实只是在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) |
app:layout_constraintCircle="@id/btn_pattern"
app:layout_constraintCircleRadius="20dp"
app:layout_constraintCircleAngle="0"
效果图如下,可以看到角度是0的话默认是在正上方的,改为90就跑到右边去了,所以应该是顺时针了。
而且能看出距离是以控件的中心点来比较的。
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">
下边的介绍都是以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()
看下源码里咋设置的,注意下,它的默认值好多都是无穷大的,所以
必须设置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
就是可以设置两张图片,然后通过设置色相饱和度等值使得一个可见一个不可见
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);
}
}
一些知识点
- app:layout_constrainedWidth
这个用来约束layout_width是wrap的控件的,使其宽度不能超过两侧的约束控件,
app:layout_constrainedHeight="true" 同理,约束上下范围的
一个textview,左右两边有约束,大小是wrap,在文字太长的时候,效果可能不是自己要的,文字跑到两边的约束外边去了
比如,中间的textview文字太长的话,可以看到盖住两边的约束控件了,明显不是我们要的结果
这时候只需要简单的加个属性就ok了
app:layout_constrainedWidth="true"
<?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 .
- 再比如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下就可以拉
<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
}
}
}