1.Group 1.1版本加入
Group属性用来控制ConstraintLayout布局内,被Group关联的view的可见性。笔者试了一下,是真的只能控制可见性,别的啥也干不了。
<android.support.constraint.Group
android:id="@+id/group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
app:constraint_referenced_ids="button1,button2" />
2.Layer 2.0加入
Layer可以看做是控制它所关联的View从而能形成一个伪边界。为何是伪边界?如下图,Layer可以在关联View后,给Layer设置一个背景。看起来就像寻常的ViewGroup包裹这些View,给ViewGroup设置背景。但是Layer并不是一个ViewGroup,且与它所关联的View处于同一个层级,因此能很好的减少一层布局。
<android.support.constraint.helper.Layer
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:id="@+id/layer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/frame"
android:padding="32dp"
app:constraint_referenced_ids="button4,button5,button3" />
avatar
所以,在不需要对背景做整体View动画如旋转、透明度、位移的情况下,Layer和Group结合可以很好的解决背景和整体可见性的问题,这种场景下,能很大程度的减少View布局嵌套。
3.ConstraintHelper
Group和Layer都是一种Helper,继承于ConstraintHelper,顾名思义,可以看做是View的帮助类,但是ConstraintHelper本身又是继承于View,所以虽然作为Helper,但是可以直接在XML里使用,并使用相关属性和属性API。
观察ConstraintHelper源码:
public abstract class ConstraintHelper extends View {
//helper所关联View id集合
protected int[] mIds = new int[32];
//helper所关联View数量
protected int mCount;
protected Context myContext;
protected Helper mHelperWidget;
protected boolean mUseViewMeasure = false;
protected String mReferenceIds;
//helper所关联View集合
private View[] mViews = null;
private HashMap<Integer, String> mMap = new HashMap();
}
那么Helper是如果关联上View进而管理它们的呢?
(1)构造器里调用初始化init()函数
ConstraintLayout_Layout_constraint_referenced_ids
通过该属性可以获得Helper所关联的View id串。进而调用setIds,addID等函数,解析出每一个真正的View所对应的id。并id信息存储于mIds等集合中。
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);
}
}
}
}
(2)Helper绘制过程
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (this.mUseViewMeasure) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} else {
this.setMeasuredDimension(0, 0);
}
}
public void onDraw(Canvas canvas) {
}
onMeasure里其实啥也没干,mUseViewMeasure默认为false,因此如果自定义Helper不设置这个值的话,Helper在xml里的宽高并没有影响,也没有任何作用,最终设置进去的都是0。
onDraw里直接是空,Helper虽然是View,但是并没有绘制上的性能损耗。
那么如何使用Helper来帮助我们管理View呢?答案就是在Helper提供的几个updateXX,updateXX与Helper的绘制生命周期所关联,因此自定义的Helper想要达到效果,必须触发绘制过程或者绘制过程的对应阶段。
public void updatePreLayout(ConstraintLayout container) {
}
public void updatePreLayout(ConstraintWidgetContainer container, , Helper helper, SparseArray<ConstraintWidget> map
}
public void updatePostLayout(ConstraintLayout container) {
}
public void updatePostMeasure(ConstraintLayout container) {
}
public void updatePostConstraints(ConstraintLayout constainer) {
}
(3)自定义Helper
Group和Layer都是属于一种自定义Helper,均继承与ConstraintHelper。所以再来看它们分别都干了些啥,导致有那些功能的。
- Group
public void updatePreLayout(ConstraintLayout container) {
//获得当前Helper的可见属性
int visibility = this.getVisibility();
float elevation = 0.0F;
if (VERSION.SDK_INT >= 21) {
elevation = this.getElevation();
}
if (this.mReferenceIds != null) {
this.setIds(this.mReferenceIds);
}
//遍历关联的View,将helper的可见属性一一赋给它们
for(int i = 0; i < this.mCount; ++i) {
int id = this.mIds[i];
View view = container.getViewById(id);
if (view != null) {
view.setVisibility(visibility);
if (elevation > 0.0F && VERSION.SDK_INT >= 21) {
view.setElevation(elevation);
}
}
}
}
- Layer
- Layer是如何包裹住关联的View?
public void updatePostLayout(ConstraintLayout container) {
this.reCacheViews();
this.mComputedCenterX = 0.0F / 0.0;
this.mComputedCenterY = 0.0F / 0.0;
LayoutParams params = (LayoutParams)this.getLayoutParams();
ConstraintWidget widget = params.getConstraintWidget();
widget.setWidth(0);
widget.setHeight(0);
//计算Layer中心点的位置
this.calcCenters();
int left = (int)this.mComputedMinX - this.getPaddingLeft();
int top = (int)this.mComputedMinY - this.getPaddingTop();
int right = (int)this.mComputedMaxX + this.getPaddingRight();
int bottom = (int)this.mComputedMaxY + this.getPaddingBottom();
//跟据计算的中间点的位置Layout自身,自此Layer的大小和位置就确定,再设置Background就是顺其自然的事了
this.layout(left, top, right, bottom);
if (!Float.isNaN(this.mGroupRotateAngle)) {
this.transform();
}
}
protected void calcCenters() {
if (this.mContainer != null) {
//Float.isNaN() isNaN->is Not Number 测量一个值是不是科学数字
// 默认mComputedCenterX = 0.0F / 0.0是非法数字
if (this.mNeedBounds || Float.isNaN(this.mComputedCenterX) || Float.isNaN(this.mComputedCenterY)) {
if (!Float.isNaN(this.mRotationCenterX) && !Float.isNaN(this.mRotationCenterY)) {
this.mComputedCenterY = this.mRotationCenterY;
this.mComputedCenterX = this.mRotationCenterX;
} else {
View[] views = this.getViews(this.mContainer);
int minx = views[0].getLeft();
int miny = views[0].getTop();
int maxx = views[0].getRight();
int maxy = views[0].getBottom();
//遍历子View,min值能算出Layer的left和top
//max值能算出layer的right和bottom,不由得感叹真是奇思妙想
for(int i = 0; i < this.mCount; ++i) {
View view = views[i];
minx = Math.min(minx, view.getLeft());
miny = Math.min(miny, view.getTop());
maxx = Math.max(maxx, view.getRight());
maxy = Math.max(maxy, view.getBottom());
}
this.mComputedMaxX = (float)maxx;
this.mComputedMaxY = (float)maxy;
this.mComputedMinX = (float)minx;
this.mComputedMinY = (float)miny;
if (Float.isNaN(this.mRotationCenterX)) {
this.mComputedCenterX = (float)((minx + maxx) / 2);
} else {
this.mComputedCenterX = this.mRotationCenterX;
}
if (Float.isNaN(this.mRotationCenterY)) {
this.mComputedCenterY = (float)((miny + maxy) / 2);
} else {
this.mComputedCenterY = this.mRotationCenterY;
}
}
}
}
}
Layer还支持对关联View的位移、旋转、缩放等操作,但是使用的时候得小心,这些操作的中心点都是基于Layer的中心点
public void setRotation(float angle) {
this.mGroupRotateAngle = angle;
this.transform();
}
public void setScaleX(float scaleX) {
this.mScaleX = scaleX;
this.transform();
}
public void setScaleY(float scaleY) {
this.mScaleY = scaleY;
this.transform();
}
public void setPivotX(float pivotX) {
this.mRotationCenterX = pivotX;
this.transform();
}
public void setPivotY(float pivotY) {
this.mRotationCenterY = pivotY;
this.transform();
}
public void setTranslationX(float dx) {
this.mShiftX = dx;
this.transform();
}
public void setTranslationY(float dy) {
this.mShiftY = dy;
this.transform();
}
private void transform() {
if (this.mContainer != null) {
if (this.mViews == null) {
this.reCacheViews();
}
this.calcCenters();
double rad = Math.toRadians((double)this.mGroupRotateAngle);
//每次操作都会关联位移、缩放、旋转操作叠加效果,因此可以使用Layer对子View整体做一些动画
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;
// 默认情况下,mComputedCenterX和mComputedCenterY是Layer的中心点
float dx = (float)x - this.mComputedCenterX;
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);
}
}
}
网友评论