美文网首页Android-ConstraintLayout
进阶ConstraintLayout-2.0新特性

进阶ConstraintLayout-2.0新特性

作者: 白六小子 | 来源:发表于2019-09-30 14:29 被阅读0次

    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);
                }
    
            }
        }
    

    相关文章

      网友评论

        本文标题:进阶ConstraintLayout-2.0新特性

        本文链接:https://www.haomeiwen.com/subject/uwbcuctx.html