ViewStub源码分析

103 阅读2分钟

成员变量:

private int mInflatedId; //ViewStub传递给装载View的id
private int mLayoutResource; //装载View的布局

private WeakReference<View> mInflatedViewRef; //用弱引用包裹起来的被装载的View

构造方法:

public ViewStub(Context context) {
    this(context, 0);
}

/**
 * Creates a new ViewStub with the specified layout resource.
 *
 * @param context The application's environment.
 * @param layoutResource The reference to a layout resource that will be inflated.
 */
public ViewStub(Context context, @LayoutRes int layoutResource) {
    this(context, null);

    mLayoutResource = layoutResource;
}

public ViewStub(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public ViewStub(Context context, AttributeSet attrs, int defStyleAttr) {
    this(context, attrs, defStyleAttr, 0);
}

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

比较简单,直接从xml布局中读取相应的属性,并且在构造方法中就设置为GONE,setWillNotDraw这方法看注释是声明View不做onDraw绘制的。

/**
 * If this view doesn't do any drawing on its own, set this flag to
 * allow further optimizations. By default, this flag is not set on
 * View, but could be set on some View subclasses such as ViewGroup.
 *
 * Typically, if you override {@link #onDraw(android.graphics.Canvas)}
 * you should clear this flag.
 *
 * @param willNotDraw whether or not this View draw on its own
 */
public void setWillNotDraw(boolean willNotDraw) {
    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(0, 0);
}

@Override
public void draw(Canvas canvas) {
}

@Override
protected void dispatchDraw(Canvas canvas) {
}

measure()测量方法直接传入0,说明ViewStub初始化就是一个0大小的View,draw()和dispatchDraw()方法什么都没做,什么都不绘制。

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

    if (viewParent != null && viewParent instanceof ViewGroup) {
        if (mLayoutResource != 0) {
            final ViewGroup parent = (ViewGroup) viewParent;
            final View view = inflateViewNoAdd(parent);
            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");
    }
}

先获取到ViewStub的父布局,然后在判断父布局不为null的情况下,进行加载布局生成装载View(目标View或者我们真正实际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);

    if (mInflatedId != NO_ID) {
        view.setId(mInflatedId); //加载布局设置好布局的ID。
    }
    return view;
}
private void replaceSelfWithView(View view, ViewGroup parent) {
    final int index = parent.indexOfChild(this);
    parent.removeViewInLayout(this);

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

接着获取到当前ViewStub的位置index,移除掉ViewStub,添加装载View到index位置上,也就是替换原来的ViewStub。

回调方法:

/**
 * Listener used to receive a notification after a ViewStub has successfully
 * inflated its layout resource.
 *
 * @see android.view.ViewStub#setOnInflateListener(android.view.ViewStub.OnInflateListener) 
 */
public static interface OnInflateListener {
    /**
     * Invoked after a ViewStub successfully inflated its layout resource.
     * This method is invoked after the inflated view was added to the
     * hierarchy but before the layout pass.
     *
     * @param stub The ViewStub that initiated the inflation.
     * @param inflated The inflated View.
     */
    void onInflate(ViewStub stub, View inflated);
}

在inflate生成装载View的时候进行回调。