ViewStub的那些事儿

来源:互联网 发布:freebsd python 编辑:程序博客网 时间:2024/05/29 05:09

     ViewStub是一个很好用的优化控件,在一些业务复杂,layout里面的各种View很多,而且有很多View都是根据不同情况显示或者隐藏甚至用不到时,如果在Activity启动开始就加载进来时,通过打日志可以看出很慢,经常超过100ms,这样用户在使用软件时会明显感觉到卡,所以今天要介绍一个优化神器—ViewStub

     ViewStub给我第一反应就是它与merge 以及include的相同与区别是什么?相同点就是他们都是辅助性的工具,用来过渡的,都可以写在xml里面,区别是merge和include只能写在xml里面,写完运行后android自动解析,而ViewStub还可以代码里调用,因为它继承自View,做一些事情,再者他们的作用和应用场景不同,include的作用是为了xml能够复用,我这个layout调用了公共的xml,那个也可以调用前面同样的xml,只需要在自己的代码include就行,而在实践中发现有时layout的层数太多,缺点是include的顶层与加载它的父layout中包含include的那个类型相同时,没办法自动去掉重复,怎么办?merge解决了这个问题,merge有include的功能,就是把自己模块化,并且自己完成任务以后不再是一个层,这样就解决了重复层这个问题,在实践中发现,有的View有时需要显示,有时隐藏,甚至有时用不到,那么基本性能考虑,如果隐藏或者用不到,我能不能不加载呢?这样的想法确实很好,而merge和include解决不了,怎么办?那就是我们的ViewStub要出场了,ViewStub刚好可以解决这个问题,它先在某个你指定的地方占个位,当你需要时,你可把自己想要的View加载上来,如果不需要,只让它在那里占个位,内存小,这就是按需加载(这是我对三者的相同与区别,有不同意见的,欢迎在下面评论,让我也补补,哈哈),为了兄弟们看了更清楚,我弄了张图:


从上图可以看出,ViewStub可写在xml里面,那要怎么写呢?接下来我们就要研究一下它,ViewStub在xml里面最显著的两个属性:android:inflatedId和android:layout,前者是将来要填充View的id,我一般都是ViewStub的id相同,后者是将来要填充View的xml文件

android:inflatedId对应ViewStub代码里的setInflatedId()函数,而android:layout对应该ViewStub代码里的setLayoutResource()函数,意思就是实现了xml的相应属性,在后面代码里就可以不调用相应的代码,因为重复了。而xml的ViewStub里设置的LayoutParams属性值都会被后面填充进来的View给沿用,当时我做项目时就以为ViewStub都被删除了,应该它的LayoutParams属性也应该没有了,后来看源码,发现沿用了,如下代码:

finalViewGroup.LayoutParamslayoutParams= getLayoutParams();
if
(layoutParams != null) {
    parent.addView(view
, index,layoutParams);
} else {
    parent.addView(view
, index);
}

上面代码可以见于ViewStub. inflate()函数内

所以要把layout_width,layout_height,Margin, layout_below,layout_centerInParent之类的都设置到ViewStub属性里,这样会被将来被填充的View给沿用。

   ViewStub因为继承自View,所以View能有的属性和函数它都有,这就不介绍了,现在我们来看看,它的特别之处: setOnInflateListener()和inflate()两函数

setOnInflateListener()函数,咱们看看源码:

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 = new WeakReference<View>(view);if (mInflateListener != null) {    mInflateListener.onInflate(this, view);}return view;

看到了倒数2-4行了么,这个就是,也就是它干完所以的事以后,当ViewStub.inflate()函数返回view之前做一个回调,我个人暂时没有发现它的妙处在哪,因为view返回以后,也可以自己知道了。

   接下来,我要重点讲下ViewStub. inflate()函数,它就是关键的那么一下,代码如下:

public View inflate() {    final ViewParent viewParent = getParent();//拿到ViewStub的父ViewGroup    if (viewParent != null && viewParent instanceof ViewGroup) {        if (mLayoutResource != 0) {            final ViewGroup parent = (ViewGroup) viewParent;            final LayoutInflater factory;            if (mInflater != null) {                factory = mInflater;            } else {                factory = LayoutInflater.from(mContext);            }
              //下面就是把之前android:layout设置的撑开得到view            final View view = factory.inflate(mLayoutResource, parent,                    false);            if (mInflatedId != NO_ID) {                view.setId(mInflatedId);            }                      //上面就是把android:inflatedId设置的给设置上            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);            }                    //上面就是沿用xml里面ViewStub设置的ViewGroup.LayoutParams属性            mInflatedViewRef = new WeakReference<View>(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. inflate()与ViewStub.setVisibility()都可以实现在view没有撑开之前,调用后撑开,但还是有区别,咱们来看看ViewStub. setVisibility()源码:

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

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

            inflate();

        }

看到这段后,我就觉得使用ViewStub. setVisibility()好一些,为什么?因为当ViewStub不需要时,而上层又setVisibility()并传View.GONE时,这时不会创建撑开View,省了!第二个原因是它如果已经撑开过,如果这时再调用inflate()会得到这个异常:thrownew IllegalStateException("ViewStub must have a non-null ViewGroupviewParent");而ViewStub.setVisibility()这个不会。

   下面把知道分享完了,把例子代码也贴贴:

Xml:

<ViewStub    android:id="@+id/myViewStub"    android:layout_width="80dp"    android:layout_height="80dp"    android:layout_marginTop="20dp"    android:layout_centerInParent="true"    android:layout="@layout/layout_img"/>

代码:

ViewStub myViewStub=(ViewStub)findViewById(R.id.myViewStub);myViewStub.setOnInflateListener(new ViewStub.OnInflateListener() {    @Override    public void onInflate(ViewStub stub, View inflated) {        Log.e("ViewStub","onInflate is invoking !!!");    }});myViewStub.setVisibility(View.VISIBLE);

 

    好了,以上都是我在实践中的得到经验,分享给大家!

                                                                           

0 0