ViewStub--使用介绍

(1)什么时候使用ViewStub?为什么使用ViewStub?

当我们需要根据某个条件控制某个View的显示或者隐藏的时候,通常是把可能用到的View都写在布局上,然后设置可见性为View.GONE或View.InVisible ,之后在代码中根据条件动态控制可见性。虽然操作简单,但是耗费资源,因为即便该view不可见,仍会被父窗体绘制,仍会创建对象,仍会被实例化,仍会被设置属性。

而android.view.ViewStub,是一个大小为0 ,默认不可见的控件,只有给他设置成了View.Visible或调用了它的inflate()之后才会填充布局资源,也就是说占用资源少。所以,推荐使用viewStub

(2)ViewStub基本介绍

官方文档地址:https://developer.android.com/reference/android/view/ViewStub.html

ViewStub 继承自View。

官方文档原文:A ViewStub is an invisible, zero-sized View that can be used to lazily inflate layout resources at runtime. When a ViewStub is made visible, or when inflate() is invoked, the layout resource is inflated. The ViewStub then replaces itself in its parent with the inflated View or Views. Therefore, the ViewStub exists in the view hierarchy until setVisibility(int) or inflate() is invoked. The inflated View is added to the ViewStub's parent with the ViewStub's layout parameters. Similarly, you can define/override the inflate View's id by using the ViewStub's inflatedId property. For instance:

我的翻译:

ViewStub 是一个不可见的,大小为0的视图,可以在运行过程中延时加载布局资源。当ViewStub被设置成可见,或者它的inflate() 方法被调用的时候,布局资源才会被填充,然后ViewStub本身就会被填充起来的布局资源替换掉。也就是说 ViewStub 被设置成可见或者它的inflate() 方法被调用之后,在视图树中就不存在了。被填充的布局在替换ViewStub的时候会使用ViewStub的布局参数(LayoutParameters),比如 width ,height等。此外,你也可以通过ViewStub的inflateId 属性定义或者重写 被填充布局资源的id。

 <ViewStub android:id="@+id/stub"
               android:inflatedId="@+id/subTree"
               android:layout="@layout/mySubTree"
               android:layout_width="120dip"
               android:layout_height="40dip" />

官方文档原文:The ViewStub thus defined can be found using the id "stub." After inflation of the layout resource "mySubTree," the ViewStub is removed from its parent. The View created by inflating the layout resource "mySubTree" can be found using the id "subTree," specified by the inflatedId property. The inflated View is finally assigned a width of 120dip and a height of 40dip. The preferred way to perform the inflation of the layout resource is the following:

我的翻译:

在上面的示例代码中,可以通过id 获取到ViewStub,当layout属性引用的布局资源 mySubTree 被填充之后,ViewStub就会从它的父窗体中移除,取而代之的就是mySubTree。通过inflatedId 属性的值可以获取到mySubTree。mySubTree 在替代ViewSub的时候会使用ViewStub的layoutParametes, 也就是说mySubTree 的宽高会被定义成 120dp 、40dp。 推荐用如下方式去实现mySubTree布局资源的填充:

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

When inflate() is invoked, the ViewStub is replaced by the inflated View and the inflated View is returned. This lets applications get a reference to the inflated View without executing an extra findViewById().

我的翻译:

当ViewStub 的inflate() 方法被调用之后,ViewStub就会被填充起来的布局替换掉,并返回填充起来的View。这样,当我们想使用被填充起来的View时就不再需要调用findViewById () 方法。(实际使用的时候,如果我们需要操作被填充布局里面的数据时用inflate(),否则可以直接使用setVisibility)

(3)示例代码:

最终效果展示:

ViewStub.gif
  • activity_viewstub_test02.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical">
    <LinearLayout android:layout_width="match_parent"
                  android:layout_height="wrap_content">
        <Button
            android:id="@+id/btn_vs_showView"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="显示ViewStub"/>
        <Button
            android:id="@+id/btn_vs_changeHint"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="更改ViewStub"/>
        <Button
            android:id="@+id/btn_vs_hideView"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_weight="1"
            android:text="隐藏ViewStub"/>
    </LinearLayout>


    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="正在加载。。。"/>

    <!--ViewStub 展示或者隐藏内容-->
    <ViewStub
        android:id="@+id/viewstub_test"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:inflatedId="@+id/iv_VsContent"
        android:layout="@layout/iv_vs_content"/>

</RelativeLayout>
  • ViewStubTestActivitiy.java
/**
 * Created by CnPeng on 2017/1/11. ViewStub 的使用示例
 */

public class ViewStubTestActivitiy extends AppCompatActivity implements View.OnClickListener {

    private ViewStub viewStub;
    private TextView hintText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_viewstub_test02);

        init();
    }

    /**
     * 初始化
     */
    private void init() {
        viewStub = (ViewStub) findViewById(R.id.viewstub_test);

        Button btn_show = (Button) findViewById(R.id.btn_vs_showView);
        Button btn_hide = (Button) findViewById(R.id.btn_vs_hideView);
        Button btn_change = (Button) findViewById(R.id.btn_vs_changeHint);

        btn_show.setOnClickListener(this);
        btn_hide.setOnClickListener(this);
        btn_change.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_vs_showView:

                //inflate 方法只能被调用一次,因为调用后viewStub对象就被移除了视图树;
                // 所以,如果此时再次点击显示按钮,就会崩溃,错误信息:ViewStub must have a non-null ViewGroup viewParent;
                // 所以使用try catch ,当此处发现exception 的时候,在catch中使用setVisibility()重新显示
                try {
                    View iv_vsContent = viewStub.inflate();     //inflate 方法只能被调用一次,
                    hintText = (TextView) iv_vsContent.findViewById(R.id.tv_vsContent);
                    //                    hintText.setText("没有相关数据,请刷新");
                } catch (Exception e) {
                    viewStub.setVisibility(View.VISIBLE);
                } finally {
                    hintText.setText("没有相关数据,请刷新");
                }
                break;
            case R.id.btn_vs_hideView:  //如果显示
                viewStub.setVisibility(View.INVISIBLE);
                break;
            case R.id.btn_vs_changeHint:
                if (hintText!=null) { 
                    hintText.setText("网络异常,无法刷新,请检查网络");
                }
                break;
        }
    }
}


  • iv_vs_content.xml --要通过ViewStub展示出来的内容
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <ImageView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:src="@mipmap/ic_launcher"/>

    <TextView
        android:id="@+id/tv_vsContent"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp30"
        android:gravity="center"
        android:text="eeee"/>
</LinearLayout>

(4) 使用总结

总结:

1)ViewStub 引用布局时使用layout 属性,取值@layout/xxxxx

2)InflateId 表示给被引用的布局的id。也就是被控制显示或隐藏的布局的id.

3)如果不写InflateId ,如果需要的话也可以直接在被引用的布局中给出id属性

4)inflate() 方法只能被调用一次,如果再次调用会报异常信息 ViewStub must have a non-null ViewGroup viewParent

这是因为,当ViewStub 调用inflate() 将其引用的 布局/view 展示出来之后,ViewStub本身就会从视图树中被移除,此时viewStub 就获取不到他的 父布局, 而 inflate() 方法中,上来就需要获取它的父布局,然后根据父布局是否为空再去执行具体的填充逻辑,如果为空就报上面的错,所以,inflate() 之后如果还想再次显示ViewStub 引用的布局/view 就需要 在调用inflate() 的时候try catch,当 catch 到异常的时候,调用setVisibility()设置viewStub 的View.Visible即可。ViewStub类中Inflate() 的具体逻辑如下:

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

    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);
            }
            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;
        } else {
            throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
        }
    } else {
        throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
    }
}  

5) ViewStub的setVisibility()中也调用了inflate(),但是为什么多次调用setVisibility()不会导致崩溃呢?

ViewStub 的setVisibility() 方法中,会先判断 WeakReference 类型的成员变量 mInflatedViewRef 是否为空。第一次调用setVisibility()的时候,mInflatedViewRef并没有初始化,也就是说是null,那么这时候就会走inflate(),在inflate() 方法中给被填充起来的布局/view创建一个WeakReference弱引用,并赋值给mInflatedViewRef,从而完成mInflatedViewRef的初始化。当第二次走setVisibility() 的时候,mInflatedViewRef已经不再是null,就会调用 WeakReference 的父类Reference 中的get() 方法获取该引用指向的实体对象,也就是说通过get() 拿到 被填充的view对象,然后再走View类的setVisibility()。ViewStub类中的setVisibility()具体实现如下:

@Override
@android.view.RemotableViewMethod
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();
        }
    }
}
  1. 根据上面 第4点和第5点可以得出如下结论:

(1) ViewStub 的inflate() 只能被调用一次!

(2) 如果想控制/修改 被填充布局中的内容并重复显示被填充的view,就用try 将viewStub.inflate() 以及修改内容的代码包裹起来,并在catch 中setVisibility.

7) 在xml 中定义ViewStub 节点时,内部不能包含其他节点,也就是说,ViewStub 是一个自闭合节点,如果一个布局/view如果想通过ViewStub显示,只能定义在单独的xml 文件中。

版权声明:

****以上内容均为原创,如需转载请注明出处****
如文章中有不当之处,敬请指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容