ConstraintHelper中的一个坑

作者: 真心czx | 来源:发表于2020-04-15 23:23 被阅读0次

    用过ConstraintLayout的都说好。
    看看我另一篇ConstraintLayout的使用教程

    但是插件化中,使用ConstraintLayout有点不如意了。
    在1.1.3版本中,继承于ConstraintHelper的Group和Barrier都无法正常起效!之前需求急就没去管,反正是有其它办法做到这两个的功能的。

    这两天研究了下原因以及解决方法。
    看一下源码,调试一下,发现原因确实挺简单的。

    我们直接用举例子的形式来分析好了。
    首先,在xml定义Group,用constraint_referenced_ids标签来为Group添加一组ViewId,当前是tv_load_error,btn_reload

    也可以通过group.setReferencedIds(int[]) 来添加。

        <androidx.constraintlayout.widget.Group
            android:id="@+id/group_load"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:visibility="gone"
            app:constraint_referenced_ids="tv_load_error,btn_reload" />
    

    也就是正常来说我们可以通过group_load这个Group来控制这两个view的显示与否。当发现通过group_load.setVisible()并未能起作用。

    打了下断点,发现group_load的引用id列表为空!!这咋回事呢?

    直击源码:
    看下ConstraintHelper初始化:

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

    读取属性标签ConstraintLayout_Layout_constraint_referenced_ids获取字符串"tv_load_error,btn_reload" 然后执行setIds()

    注意,tv_load_error 现在只是viewId相对应的名称,现在我们需要通过这个名称去获取viewId (例如:0x7fxxxxxx)

        private void setIds(String idList) {
            if (idList != null) {
                int begin = 0;
                while(true) {
                    int end = idList.indexOf(',', begin);
                    if (end == -1) {
                        this.addID(idList.substring(begin));
                        return;
                    }
                    this.addID(idList.substring(begin, end));
                    begin = end + 1;
                }
            }
        }
    

    就是对tv_load_errorbtn_reload分别执行addID()

        private void addID(String idString) {
            if (idString != null) {
                if (this.myContext != null) {
                    idString = idString.trim();
                    int tag = 0;
    
                    //1
                    try {
                        Class res = id.class;
                        Field field = res.getField(idString);
                        tag = field.getInt((Object)null);
                    } catch (Exception var5) {
                    }
                    
                    //2
                    if (tag == 0) {
                        tag = this.myContext.getResources().getIdentifier(idString, "id", this.myContext.getPackageName());
                    }
    
                    //3
                    if (tag == 0 && this.isInEditMode() && this.getParent() instanceof ConstraintLayout) {
                        ConstraintLayout constraintLayout = (ConstraintLayout)this.getParent();
                        Object value = constraintLayout.getDesignInformation(0, idString);
                        if (value != null && value instanceof Integer) {
                            tag = (Integer)value;
                        }
                    }
                    if (tag != 0) {
                        this.setTag(tag, (Object)null);
                    } else {
                        Log.w("ConstraintHelper", "Could not find id of \"" + idString + "\"");
                    }
    
                }
            }
        }
        
        public void setTag(int tag, Object value) {
            if (this.mCount + 1 > this.mIds.length) {
                this.mIds = Arrays.copyOf(this.mIds, this.mIds.length * 2);
            }
    
            this.mIds[this.mCount] = tag;
            ++this.mCount;
        }
    

    通过注释,有三个地方可以获得tag, 只要tag!=0 那么就可以执行setTag(), 这时group才算是真正持有了tv_load_errorbtn_reload相对应的viewId。

    注释1是什么情况呢?

    直接读id.class 即: androidx.constraintlayout.widget.R.id;
    这个由ConstrainLayout库生成的R文件显然是不会有我们自己定义的id。

    在build中找到这个文件

        public static final class id {
            public static final int bottom = 0x7f09006f;
            public static final int end = 0x7f0900cc;
            public static final int gone = 0x7f0900ef;
            public static final int invisible = 0x7f090133;
            public static final int left = 0x7f090144;
            public static final int packed = 0x7f09021d;
            public static final int parent = 0x7f090224;
            public static final int percent = 0x7f090227;
            public static final int right = 0x7f09025c;
            public static final int spread = 0x7f090299;
            public static final int spread_inside = 0x7f09029a;
            public static final int start = 0x7f09029f;
            public static final int top = 0x7f0902cf;
            public static final int wrap = 0x7f0902fd;
        }
    

    emmm... 就是几个通用值,显然没有我们要找的tv_load_error

    再来看注释3

    是的,先看3,
    毕竟this.isInEditMode()是否足以让我们直接排除3了,毕竟现在是处于运行时,而不是编辑模式。

    注释2

    所以我们现在集中精力,来看注释2,花上2分钟攻克它。

    就一行代码

    tag = this.myContext.getResources().getIdentifier(idString, "id", 
                this.myContext.getPackageName());
    

    idString是tv_error, this.myContext.getPackageName()是包名.假设为 com.handsome.isme

    这行代码会从com.handsome.isme这个应用的资源中查找名称为tv_error的id值。

    正常情况下,是可以找到。除非...

    是的,就是插件化的原因。我定义的这个tv_error所在的插件的包名id是与宿主包名是不同,而我们这里仅是从宿主中寻找tv_error, 那肯定找不到了..

    也不能说肯定找不到..万一宿主也定义了一个tv_error,但是两者id肯定不同,所以也是毫无作用的,万一findViewById没判断,还就得崩了呢...

    所以咋办呢?

    解决方法:

    1. 思路简直不要太简单了
        val tvError = contentView.findViewById<View>(R.id.tv_load_error)
        val reloadBtn = contentView.findViewById<View>(R.id.btn_reload)
        group = contentView.findViewById(R.id.group_load) 
        group?.referencedIds = intArrayOf(tvError.id, reloadBtn.id)
    

    初始化读不到值,那我手动赋值总行了吧...

    1. 自定义Group。

    在addID()那一个方法中,修改注释2的获取ViewId的实现。

    a. 当从宿主中找不到此viewId时,遍历所有插件进行寻找(传入所有插件的包名)

    不过,还是有问题的...就是可能找到其他插件的相同名称的viewId...

    b. ConstraintHelper肯定是ConstraintsLayout的子View。通过获取parent,读取parent的所有子View的ViewId,通过ViewId反查其名称

        res = resources.getResourceEntryName(child.getId());
    

    判断名称相等即可。

    当然这种方法,可以直接修改ConstraintHelper文件的字节码来实现,这样我们就可以正常使用所有的ConstraintHelper控件了

    1. 过阵子我再告诉你

    为什么是过阵子呢?因为只要升级ConstraintLayout至2.0就可以解决这个问题了。其解决方式就是方法2中b方案。
    只是ConstraintLayout的2.0正式版本还没有发布。

    相关文章

      网友评论

        本文标题:ConstraintHelper中的一个坑

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