View - merge include viewstub

449 阅读3分钟

android 28源码

总结(从xml解析view的情况)

1 View的属性(非layout开头的属性)比如padding、background是在View的构造方法中初始化的。

2 View的layout_xxx属性是通过父view创建的(LayoutInflater中调用ViewGroup.generateLayoutParams(attrs) ),是在View构造之后设置的。

3 merge丢失所有属性。

4 include可以设置layout属性和id,如果使用layout属性必须设置宽高;

5 ViewStub是占位view,layout属性在ViewStub中设置,id优先使用ViewStub的inflatedId

一 merge 不是view(view和layout属性都丢失)

1 必须是根节点,必须添加到父容器

2 merge不是view,属性会丢失。

LayoutInflater.java 
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { 
    ...... 
    synchronized (mConstructorArgs) { 
        // merge标签 
        if (TAG_MERGE.equals(name)) { 
            // 必须有父容器,必须attach 
            if (root == null || !attachToRoot) { 
                throw new InflateException("<merge /> can be used only with a valid " + "ViewGroup root and attachToRoot=true"); } rInflate(parser, root, inflaterContext, attrs, false); 
            } 
            // 解析merge子布局 
            rInflate(parser, root, inflaterContext, attrs, false); 
        }
    }
}

二 ViewStub (是view,占位view,View属性无效,layout_xxx属性以ViewStub为准,id以ViewStub为主)

<ViewStub android:id="@+id/view_stub" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
    android:inflatedId="@+id/view_stub_parent" 
    android:layout="@layout/view_stub" 
    app:layout_constraintBottom_toBottomOf="parent" 
    app:layout_constraintEnd_toEndOf="parent" />

1 占位view。

2 需要时inflate()进行初始化,多次inflate会崩溃。

3 layout_xxx属性,以ViewStub中为准。ViewStub必须包含layout_width和layout_height属性。

4 设置View的属性无效。

5 View的id以ViewStub中inflatedId为主。

ViewStub 通过设置GONE 以及设置宽和高都为0,以及调用函数setWillNotDraw(true)来达到自己不绘制,不渲染在界面的效果,其实仅仅就是作为一个占着坑的意思。


public final class ViewStub extends View { 
    public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 
        super(context);
        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewStub, defStyleAttr, defStyleRes); 
        saveAttributeDataForStyleable(context, R.styleable.ViewStub, attrs, a, defStyleAttr, defStyleRes); 
        mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID); 
        mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0); 
        mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
        a.recycle(); 
        // 不可见 
        setVisibility(GONE); 
        // 不参与绘制 
        setWillNotDraw(true); 
    }
    
    public View inflate() { 
        final ViewParent viewParent = getParent(); 
        if (viewParent != null && viewParent instanceof ViewGroup) {
            if (mLayoutResource != 0) { 
                final ViewGroup parent = (ViewGroup) viewParent; 
                // 从xml中实例化新view
                final View view = inflateViewNoAdd(parent); 
                // 新view替换viewStub
                replaceSelfWithView(view, parent); 
                mInflatedViewRef = new WeakReference<>(view); 
                if (mInflateListener != null) { 
                    mInflateListener.onInflate(this, view); 
                } 
                return view; 
            } else { 
                throw new IllegalArgumentException("ViewStub must have a valid layoutResource"); 
            } 
        } else { 
            // 第二次调用inflate会崩溃 
            throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
        } 
    } 

    // 实例化新View
    private View inflateViewNoAdd(ViewGroup parent) { 
        final LayoutInflater factory; 
        if (mInflater != null) { 
            factory = mInflater; 
        } else {
            factory = LayoutInflater.from(mContext); 
        } 
        final View view = factory.inflate(mLayoutResource, parent, false); 
        // 设置view的id,使用inflateId 
        if (mInflatedId != NO_ID) { 
            view.setId(mInflatedId); 
        } 
        return view; 
    } 
    
    // 替换ViewStub
    private void replaceSelfWithView(View view, ViewGroup parent) { 
        final int index = parent.indexOfChild(this); 
        // 把ViewStub从父容器移除了 
        parent.removeViewInLayout(this); 
        // 属性设置 
        final ViewGroup.LayoutParams layoutParams = getLayoutParams(); 
        if (layoutParams != null) { 
            parent.addView(view, index, layoutParams); 
        } else {
            parent.addView(view, index);
        } 
    } 
}

三 Include标签(不是view)

<include layout="@layout/view_merge" 
    android:layout_width="50dp" 
    android:layout_height="200dp" 
    app:layout_constraintBottom_toBottomOf="parent" />
LayoutInflater.java

private void parseInclude(XmlPullParser parser, Context context, View parent, AttributeSet attrs) throws XmlPullParserException, IOException { 
    ......
    // 使用include的xml 
    final XmlResourceParser childParser = context.getResources().getLayout(layout); 
    // 使用include的xml中属性 
    final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
    ...... 
    final String childName = childParser.getName(); 
    if (TAG_MERGE.equals(childName)) { 
        // The <merge> tag doesn't support android:theme, so 
        // nothing special to do here. 
        // 处理merge标签
        rInflate(childParser, parent, context, childAttrs, false); 
     } else { 
        // 使用被include的xml属性创建view
        final View view = createViewFromTag(parent, childName, context, childAttrs, hasThemeOverride); 
        final ViewGroup group = (ViewGroup) parent; 
        final TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.Include); 
        // 获得include的id(注意这里使用不是childAttrs,而是父的)
        final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID); 
        final int visibility = a.getInt(R.styleable.Include_visibility, -1); 
        a.recycle(); 
        ViewGroup.LayoutParams params = null; 
        try { 
            // 先使用include中LayoutParams属性。无设置宽高,抛异常。 
            params = group.generateLayoutParams(attrs); 
        } catch (RuntimeException e) { 
            // Ignore, just fail over to child attrs. 
        } 
        if (params == null) { 
            // 如果include中无设置宽高,抛异常;使用子布局的LayoutParams。
            params = group.generateLayoutParams(childAttrs); 
         } 
         view.setLayoutParams(params); 
         // Inflate all children. 
         rInflateChildren(childParser, view, childAttrs, true); 
         // 设置root的id(使用include的id)
         f (id != View.NO_ID) { 
             view.setId(id); 
         } 
         ...... 
    } 
    ......
}

1 root的id设置(优先使用include标签的)

如果include中设置了id,那么就通过include的id来查找被include布局根元素的View;如果include中没有设置Id, 而被include的布局的根元素设置了id,那么通过该根元素的id来查找该view即可。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:orientation="vertical" > 
    <include 
        android:id="@+id/my_title_ly" 
        android:layout_width="match_parent" 
        android:layout_height="wrap_content" 
        layout="@layout/my_title_layout" />
</LinearLayout>

2 root的layout属性设置(优先include标签,但include标签中必须有宽高layout属性才生效)

先使用include标签中layout参数,再使用被include的xml中layout参数。

如果include标签中没有设置android:layout_width和android:layout_height,就会使用include的xml中layout参数。