想让子view可以设置margin,写继承ViewGroup的自定义View时为啥要重写generateLayoutParams(AttributeSet attrs)

转自:https://www.jianshu.com/p/99c27e2db843

重写generateLayoutParams()方法:
想让子view可以设置margin,写继承ViewGroup的自定义View时为啥要重写generateLayoutParams(AttributeSet attrs)_第1张图片
LayoutParams是ViewGroup的一个内部类,它的构造方法:

       public LayoutParams(Context c, AttributeSet attrs) {
            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);
            setBaseAttributes(a,
                    R.styleable.ViewGroup_Layout_layout_width,
                    R.styleable.ViewGroup_Layout_layout_height);
            a.recycle();
        }

        public LayoutParams(int width, int height) {
            this.width = width;
            this.height = height;
        }
  
        public LayoutParams(LayoutParams source) {
            this.width = source.width;
            this.height = source.height;
        }

        LayoutParams() {
        }

generateLayoutParams()方法返回的LayoutParams对象正是调用了它的第一个构造方法创建出来的。

接下来我们看看LayoutInflater对象inflate()方法返回View的方法:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }

        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

我们点开inflate(parser, root, attachToRoot)方法往下跟最终会看到下面的代码:

/**
     * Recursive method used to descend down the xml hierarchy and instantiate
     * views, instantiate their children, and then call onFinishInflate().
     * 

* Note: Default visibility so the BridgeInflater can * override it. */ void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { final int depth = parser.getDepth(); int type; boolean pendingRequestFocus = false; while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } final String name = parser.getName(); if (TAG_REQUEST_FOCUS.equals(name)) { pendingRequestFocus = true; consumeChildElements(parser); } else if (TAG_TAG.equals(name)) { parseViewTag(parser, parent, attrs); } else if (TAG_INCLUDE.equals(name)) { if (parser.getDepth() == 0) { throw new InflateException(" cannot be the root element"); } parseInclude(parser, context, parent, attrs); } else if (TAG_MERGE.equals(name)) { throw new InflateException(" must be the root element"); } else { final View view = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflateChildren(parser, view, attrs, true); viewGroup.addView(view, params); } } if (pendingRequestFocus) { parent.restoreDefaultFocus(); } if (finishInflate) { parent.onFinishInflate(); } }

从上面代码我们可以看出,在xml布局被解析成View对象的过程中,viewGroup.addView(view, params)传入的params就是通过viewGroup.generateLayoutParams(attrs)获得的,参数attrs里包装的就是这个view在xml中的属性,所以如果我们不重写generateLayoutParams()方法,那这个viewGroup里的子view就不支持margin设置了。

补充:
看下addView()方法源码:

public void addView(View child, int index) {
        if (child == null) {
            throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
        }
        LayoutParams params = child.getLayoutParams();
        if (params == null) {
            params = generateDefaultLayoutParams();
            if (params == null) {
                throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
            }
        }
        addView(child, index, params);
    }

看下ViewGroup的generateDefaultLayoutParams()方法源码:

 protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

从源码可以看出,当我们在代码中addView时,若子view没有LayoutParams,会通过generateDefaultLayoutParams()方法返回,而此方法ViewGroup返回的是LayoutParams对象。所以,为了能让子View设置margin,我们写继承VIewGroup的自定义view时最好也要复写generateDefaultLayoutParams()方法。

最后,我们在addViewInner()方法源码:

private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {

        if (mTransition != null) {
            // Don't prevent other add transitions from completing, but cancel remove
            // transitions to let them complete the process before we add to the container
            mTransition.cancel(LayoutTransition.DISAPPEARING);
        }

        if (child.getParent() != null) {
            throw new IllegalStateException("The specified child already has a parent. " +
                    "You must call removeView() on the child's parent first.");
        }

        if (mTransition != null) {
            mTransition.addChild(this, child);
        }

        if (!checkLayoutParams(params)) {
            params = generateLayoutParams(params);
        }

checkLayoutParams()方法源码:

protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return  p != null;
    }

checkLayoutParams和generateLayoutParams(params)这是啥意思呢
其实这是检查你的params是否是正确的,当然子类可以复写这两个条件,一般来说,checkLayoutParamsl里条件是p!=null而且不是你的自定义viewGroup的定义的layoutParams的子类,这样父view可以帮你生成一个符合其viewGroup的params,具体例子请看LinearLayout如何重写这些方法:

  /**
    xml生成
     */
@Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LinearLayout.LayoutParams(getContext(), attrs);
    }

    /**
    生成默认的
     */
    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        if (mOrientation == HORIZONTAL) {
            return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        } else if (mOrientation == VERTICAL) {
            return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        }
        return null;
    }
  /**
    addview时params搞错了,纠错
     */
    @Override
    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
        if (sPreserveMarginParamsInLayoutParamConversion) {
            if (lp instanceof LayoutParams) {
                return new LayoutParams((LayoutParams) lp);
            } else if (lp instanceof MarginLayoutParams) {
                return new LayoutParams((MarginLayoutParams) lp);
            }
        }
        return new LayoutParams(lp);
    }


    // Override to allow type-checking of LayoutParams.
   //查看params类型是否正确
    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LinearLayout.LayoutParams;
    }

其中,LinearLayout.LayoutParams是继承自ViewGroup.MarginLayoutParams的:
想让子view可以设置margin,写继承ViewGroup的自定义View时为啥要重写generateLayoutParams(AttributeSet attrs)_第2张图片

你可能感兴趣的:(android)