SetContentView流程分析

一、xml加载流程:
Activity
在这里插入图片描述
AppCompatActivity
在这里插入图片描述
Activity.setContentView流程(android 30)
在这里插入图片描述

二、LayoutInflate的参数的作用
// 方式一:将布局添加成功
View view = inflater.inflate(R.layout.inflate_layout, ll, true);

// 方式二:报错,一个View只能有一个父亲(The specified child already has a parent.)
View view = inflater.inflate(R.layout.inflate_layout, ll, true); // 已经addView
ll.addView(view);

// 方式三:布局成功,第三个参数为false
// 目的:想要 inflate_layout 的根节点的属性(宽高)有效,又不想让其处于某一个容器中
View view = inflater.inflate(R.layout.inflate_layout, ll, false);
ll.addView(view);

// 方式四:root = null,这个时候不管第三个参数是什么,显示效果一样
// inflate_layout 的根节点的属性(宽高)设置无效,只是包裹子View,
// 但是子View(Button)有效,因为Button是出于容器下的
View view = inflater.inflate(R.layout.inflate_layout, null, false);
ll.addView(view);

三、描述下merge、include、ViewStub标签的特点

  • include:
  1. 不能作为根元素,需要放在 ViewGroup中
  2. findViewById查找不到目标控件,这个问题出现的前提是在使用include时设置了id,而在findViewById时却用了被include进来的布局的根元素id。
    1. 为什么会报空指针呢?
      如果使用include标签时设置了id,这个id就会覆盖 layout根view中设置的id,从而找不到这个id
      代码:LayoutInflate.parseInclude
      –》final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
      –》if (id != View.NO_ID) {
      view.setId(id);
      }
  • merge:
  1. merge标签必须使用在根布局
  2. 因为merge标签并不是View,所以在通过LayoutInflate.inflate()方法渲染的时候,第二个参数必须指定一个父容器,且第三个参数必须为true,也就是必须为merge下的视图指定一个父亲节点.
  3. 由于merge不是View,所以对merge标签设置的所有属性都是无效的.
  • ViewStub: 就是一个宽高都为0的一个View,它默认是不可见
  1. 类似include,但是一个不可见的View类,用于在运行时按需懒加载资源,只有在代码中调用了viewStub.inflate();或者viewStub.setVisible(View.visible)方法时才内容才变得可见。
  2. 这里需要注意的一点是,当ViewStub被inflate到parent时,ViewStub就被remove掉了,即当前view hierarchy中不再存在ViewStub,而是使用对应的layout视图代替。

ViewStub.java

public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
	// 获取 InflatedId
	mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
	// 设置不显示
    setVisibility(GONE);
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	// 设置宽高为0
    setMeasuredDimension(0, 0);
}

public View inflate() {
    final ViewParent viewParent = getParent();

    if (viewParent != null && viewParent instanceof ViewGroup) {
        if (mLayoutResource != 0) {
            final ViewGroup parent = (ViewGroup) viewParent;
            // 设置ID
            final View view = inflateViewNoAdd(parent);
            // 替换自己和View
            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 {
        throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
    }
}

private View inflateViewNoAdd(ViewGroup parent) {
    if (mInflatedId != NO_ID) {
        view.setId(mInflatedId);
    }
}

private void replaceSelfWithView(View view, ViewGroup parent) {
    final int index = parent.indexOfChild(this);
    // 移除自己
    parent.removeViewInLayout(this);

    final ViewGroup.LayoutParams layoutParams = getLayoutParams();
    // 添加View
    if (layoutParams != null) {
        parent.addView(view, index, layoutParams);
    } else {
        parent.addView(view, index);
    }
}

四、相关面试题

  1. 为什么requestWindowFeature()要在setContentView()之前调用
    答:requestWindowFeature 实际调用的是 PhoneWindow.requestFeature,
    在这个方法里面会判断如果变量 mContentParentExplicitlySet 为true则报错,而这个变量会在 PhoneWindow.setContentView 调用的时候设置为true。

  2. 为什么这么设计呢?
    答:DecorView的xml布局是通过设置的窗口特征进行选择的。

  3. 为什么Activity继承AppCompatActivity中使用requestWindowFeature(Window.FEATURE_NO_TITLE);设置无效?
    答:需要用 supportRequestWindowFeature(Window.FEATURE_NO_TITLE);,因为继承的是AppCompatActivity,这个类里面会覆盖设置。

  4. 自定义view为什么必须要定义两参的构造方法
    答:一个参数的构造方法是在new的时候使用
    两个参数的构造方法是在xml解析过程中使用,通过反射进行调用
    LayoutInflate.java

@UnsupportedAppUsage
static final Class<?>[] mConstructorSignature = new Class[] {
            Context.class, AttributeSet.class};  // 这个是两个参数,所以自定义view的时候必须要定义两个参数的构造函数


@Nullable
public final View createView(@NonNull Context viewContext, @NonNull String name,
        @Nullable String prefix, @Nullable AttributeSet attrs)
        throws ClassNotFoundException, InflateException {
   		...
        if (constructor == null) {
            // Class not found in the cache, see if it's real, and try to add it
            // 通过反射创建View对象
            clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                    mContext.getClassLoader()).asSubclass(View.class);

            if (mFilter != null && clazz != null) {
                boolean allowed = mFilter.onLoadClass(clazz);
                if (!allowed) {
                    failNotAllowed(name, prefix, viewContext, attrs);
                }
            }
            // 拿构造对象
            constructor = clazz.getConstructor(mConstructorSignature);
            constructor.setAccessible(true);
            sConstructorMap.put(name, constructor);
            ...
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值