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参数。