从源码角度来看ViewStub

布局优化就会提到ViewStub,提到ViewStub印象里就是按需加载的概念,那ViewStub是怎样实现按需加载的呢?使用ViewStub和setVisibility可以实现相同的效果,那两者有什么区别呢?为什么使用ViewStub可以对布局进行优化?

ViewSub的使用

在布局文件中声明ViewStub:

<ViewStub  android:id="@+id/viewStub" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout="@layout/view"/>

在代码中使用,显示view

ViewStub view = findViewById(R.id.viewStub);
方法一:通过调用setVisibility()方法来
view.setVisibility(View.VISIBLE);
方法二:通过调用inflate()方法,可以返回ViewStub显示的view
View inflateView = view.inflate();

进入ViewStub源码

ViewStub继承View,部分源码如下:

@RemoteView
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);
        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);//设置GONE
        setWillNotDraw(true);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(0, 0);
    }
    @Override
    public void draw(Canvas canvas) {
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
    }
    @Override
    @android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync")
    public void setVisibility(int visibility) {
        if (mInflatedViewRef != null) {
            View view = mInflatedViewRef.get();
            if (view != null) {
                view.setVisibility(visibility);
            } else {
                throw new IllegalStateException("setVisibility called on un-referenced view");
            }
        } else {
            super.setVisibility(visibility);
            if (visibility == VISIBLE || visibility == INVISIBLE) {
                inflate();
            }
        }
    }
}

可以看到在ViewStub构造方法中,调用setVisibility(GONE),设置为GONE;ViewStub的draw()和dispatchDraw()方法都是空方法,没有对子视图进行绘制;在onMeasure()方法中调用了setMeasuredDimension(0, 0)对所有子视图都设置为宽高为0。从这里就可以明白ViewStub 是一个不可见的,大小为0的视图
ViewStub中复写了setVisibility()方法,在方法里调用inflate()方法。所以通过setVisibility(View.VISIBLE)显示ViewStub,最终会调用inflate()方法,只要弄明白inflate()方法就可以知道ViewStub通过什么方式来显示View的。
ViewStub的inflate()方法源码如下:

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

可以看出inflate()方法调用了inflateViewNoAdd()这个方法,这个方法最终通过调用LayoutInflater.from(xx).inflate(xx)方法把对应按需加载的View初始化,再通过调用replaceSelfWithView()方法,先把ViewStub从父布局中移除,再通过addView()将View布局添加进去从而显示View。
看到这里,对于文章开头的几个问题就都弄明白了,那么另一个问题,在使用时注意inflate()方法只能执行一次,所以ViewStub不适合重复显示隐藏的功能,那为什么呢?
mInflatedViewRef通过弱引用形式,建立ViewStub与加载的View的联系,在setVisibility()方法中mInflatedViewRef是null,那么这时候就会走inflate(),在inflate() 方法中给view创建一个WeakReference弱引用,初始化mInflatedViewRef。当第二次调用setVisibility() 的时候,mInflatedViewRef不是null,就会调用 WeakReference 的父类Reference 中的get() 方法获取到view,然后再走View类的setVisibility()方法。

android:visibility=”gone” 和 ViewStub 之间的区别

涉及到View怎样从Activity中加载显示问题,不再深入说明。在代码中调用setContentView(R.layout.activity_main)方法最终会调用LayoutInflater.inflate()方法填充布局,源码如下:

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

在LayoutInflater的inflate()方法会根据参数中的布局资源调用XmlResourceParser实现xml文件解析从而得到一个个的View,而这时候如果你把不需要马上显示的View设置成GONE一样会被解析一样会被加载到内存中去,当然你放到ViewStub中去那么就只会加载ViewStub并不会把相对应的View也加载进去,从而起到懒加载的效果,最终达到布局优化的目的。
参考文档:
https://www.jianshu.com/p/175096cd89ac
https://juejin.im/entry/59b8eaf26fb9a00a52064be7

你可能感兴趣的:(android学习笔记)