您的位置:首页 > 其它

ViewStub源码研究

2017-02-19 22:50 218 查看
周末在家,没有办法写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时要小心
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息