ViewStub源码研究

来源:互联网 发布:苹果手机频谱软件 编辑:程序博客网 时间:2024/06/07 23:57

周末在家,没有办法写demo,当然也没办法测试,只能通过源码直接分析。网上找了一个源码库,分享给大家

链接:http://repository.grepcode.com/java/ext/

ViewStub源码链接:http://www.grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/view/ViewStub.java

简介

ViewStub是一个用来延迟初始化的View的类。适用于那些Views布局复杂,或者加载比较耗时的情况,延迟初始化可以优化用户体验。

官方解释的理解

ViewStub是不可见的,0像素View,被用来在运行时推迟填充layout资源。

ViewStub调用setVisibility(View.VISIBLE)或inflate()后,会触发inflated layout被初始化。,ViewStub本身会被removed掉,inflated Layout会添加到parent layout中。


注意,inflatedlayout会使用ViewStub的layoutparameters。被remove掉后,ViewStub就没了。后续就得使用inflated ID。

ViewStub的xml布局范例

<ViewStubandroid:id="@+id/stub"

              android:inflatedId="@+id/subTree"

              android:layout="@layout/mySubTree"

              android:layout_width="120dip"

               android:layout_height="40dip"/>

解析前通过android:id->stub可以find到ViewStub,inflate()之后通过android:inflatedId->subTree也可以找到inflatedlayout。

换个方式,还可以这样。

     ViewStub stub = (ViewStub) findViewById(R.id.stub);
     View inflated = stub.inflate();

Inflatedlayout不需要通过findViewById也可以直接get到View。
这是官方推荐的获取方式。我也觉得更靠谱,避免了一些不必要的逻辑问题。

源码解析

变量定义

    private int mLayoutResource = 0;

    private int mInflatedId;

    private WeakReference<View>mInflatedViewRef;

    private LayoutInflater mInflater;

    private OnInflateListener mInflateListener;
这里需要看下红字标明的部分。

ViewStub结构体

    public ViewStub(Context context, intlayoutResource) {

        mLayoutResource = layoutResource;

        initialize(context);

    }

 

    public ViewStub(Context context,AttributeSet attrs) {

        this(context, attrs, 0);

    }

 

   @SuppressWarnings({"UnusedDeclaration"})

    public ViewStub(Context context,AttributeSet attrs, int defStyleAttr) {

        this(context, attrs, defStyleAttr, 0);

    }

 

    public ViewStub(Context context,AttributeSet attrs, int defStyleAttr, int defStyleRes) {

        TypedArray a = context.obtainStyledAttributes(

                attrs,com.android.internal.R.styleable.ViewStub, defStyleAttr, defStyleRes);

 

        mInflatedId =a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);

        mLayoutResource =a.getResourceId(R.styleable.ViewStub_layout, 0);

 

        a.recycle();

 

        a = context.obtainStyledAttributes(

                attrs,com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);

        mID =a.getResourceId(R.styleable.View_id, NO_ID);

        a.recycle();

 

        initialize(context);

}

除了mInflatedId和mLayoutResource的赋值,主要看initialize(context).

Initialize(context)

    private void initialize(Context context) {

        mContext = context;

        setVisibility(GONE);

        setWillNotDraw(true);

}

看起来一切正常。setWillNotDraw(true)你懂的,主要看setVisibility(GONE)。

setVisibility(GONE)

    @Override

    @android.view.RemotableViewMethod

    public void setVisibility(int visibility) {

        if (mInflatedViewRef != null) {

            View view = mInflatedViewRef.get();

            if (view != null) {

                view.setVisibility(visibility);

            } else {

                throw newIllegalStateException("setVisibility called on un-referenced view");

            }

        } else {

            super.setVisibility(visibility);

            if(visibility == VISIBLE || visibility == INVISIBLE) {

                inflate();

            }

        }

}

这里可以看到setVisibility被ViewStub重写。
若mInflatedViewRef保存了inflatedView的弱引用,如果什么时候这个弱引用get到一个null是要抛出IllegalStateException的。那么在使用的时候就需要注意了,这是可能会挂的。
倘若没有被inflated过,那么mInflatedViewRef肯定是null。看红色标红地方,setVisibility只有在View.GONE才是不会调用inflated的,其他情况都会启动 inflate()。

 

继续看源码

public int getInflatedId()

 

@android.view.RemotableViewMethod

public void setInflatedId(int inflatedId)

 

public int getLayoutResource()

 

@android.view.RemotableViewMethod

public void setLayoutResource(int layoutResource)

 

public void setLayoutInflater(LayoutInflater inflater)

 

public LayoutInflater getLayoutInflater()

 

@Override

protected void onMeasure(int widthMeasureSpec, intheightMeasureSpec) {

    setMeasuredDimension(0, 0);

}

 

@Override

public void draw(Canvas canvas) {

}

 

@Override

protected void dispatchDraw(Canvas canvas) {

}

这里可以看到ViewStub本身就是空白的。

另一个重头函数inflate()

    public View inflate() {

        final ViewParent viewParent =getParent();

 

        if (viewParent != null && viewParentinstanceof ViewGroup) {

            if (mLayoutResource != 0) {

                final ViewGroup parent =(ViewGroup) viewParent;

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

                }

 

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

                }

 

                mInflatedViewRef = newWeakReference<View>(view);

 

                if (mInflateListener != null) {

                    mInflateListener.onInflate(this, view);

                }

 

                return view;

            } else {

                thrownew IllegalArgumentException("ViewStub must have a validlayoutResource");

            }

        } else {

            throw new IllegalStateException("ViewStubmust have a non-null ViewGroup viewParent");

        }

}

注意看标红的code。

第一个 if(viewParent != null && viewParent instanceof ViewGroup)
说明--->ViewStub不能单独使用,必须作为ViewGroup的二级layout。

第二个 if(mLayoutResource != 0)

说明--->ViewStub如果没有inflate layout定义,那就根本没有存在的价值,直接报错了。IllegalArgument。非法主题。类型都说的很直白了。

第三个 if(mInflatedId != NO_ID) {

view.setId(mInflatedId);

}

说明--->如果没有定义android:inflatedId,那么inflated layout是不会被赋值到inflate出来的Views的

第四个 parent.removeViewInLayout(this);

说明--->ViewStub在inflate之后,会先被remove

第五个  final ViewGroup.LayoutParamslayoutParams = getLayoutParams();

       if(layoutParams != null) {

           parent.addView(view, index, layoutParams);

       }

说明--->inflatedlayout被赋予了ViewStub的layoutparameters

第六个 mInflatedViewRef= new WeakReference<View>(view);

说明--->inflatedlayout被封装在了WeakReference,各位朋友,弱引用的利弊要细细品味。既然是弱引用一些必要的判断就必不可少了。

第七个 mInflateListener.onInflate(this,view);

说明--->inflate完成后,会通过onInflate(this,view)。讲ViewStub和inflated view回调出来,通知ViewStub所在的Activity实例

小结

1.    ViewStub必须作为ViewGroup的二级view。

2.    ViewStub必须要有inflateLayout

3.    ViewStub的延迟加载原理是将必要的id保存到新view,inflate新new后,先remove ViewStub自身,再讲新View add到parent上。

4.    ViewStub利用了同样的layout parameters,所以不会让你感到闪烁,如果有这种感觉,请查一下自己的inflateLayout

5.    ViewStub利用了WeakReference保存了inflated View,弱引用是把双刃剑,在使用ViewStub时要小心

0 0