乐趣区

AndroidViewStub

ViewStub 是一个轻量级 View,它是一个看不见的,并且不占布局位置,占用资源非常小的视图对象。可以为 ViewStub 指定一个布局,加载布局时,只有 ViewStub 会被初始化,然后当 ViewStub 被设置为可见时,或者是调用了 ViewStub.inflate()时,ViewStub 所指向的布局会被加载和实例化,然后 ViewStub 的布局属性都会传给它指向的布局。这样就可以使用 ViewStub 来设置是否显示某个布局。

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

ViewStub是继承自 View,设置它的可见性,可以通过ViewStub#setVisibility()ViewStub#inflate()
先看 ViewStub#setVisibility() 方法。

private WeakReference<View> mInflatedViewRef;
 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();
            }
        }
    }

初次调用该方法,mInflatedViewRef=null。因此会先走 else 逻辑。先通过 super.setVisibility() 调用 ViewsetVisibility方法,更改相关的标志。然后无论设置的是 View.VISIBLE 或者是 View.INVISIBLE 都会去调用 ViewStub#inflate() 方法。如果 mInflatedViewRef!=null,就是正常的设置可见性。
再看 View#inflate() 方法。在 xml 布局中 ViewStub 节点下 android:layout 引入的布局会将 ViewStub 替换掉,并且 ViewStub 的宽高等属性会被该布局使用。

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

        if (viewParent != null && viewParent instanceof ViewGroup) {if (mLayoutResource != 0) {final ViewGroup parent = (ViewGroup) viewParent;
                /*
                inflateViewNoAdd()方法被填充的布局生成 View 对象。如果为 ViewStub 设置了属性 inflatedId,那么这里的 view 的 id 会被替换成该 inflatedId。*/
                final View view = inflateViewNoAdd(parent);
                // 将 ViewStub 替换成被填充的布局,并使用了 ViewStub 的属性。replaceSelfWithView(view, parent);

                mInflatedViewRef = new WeakReference<>(view);
                if (mInflateListener != null) {// 监听事件需要在 inflate()或者 setVisibility()之前调用。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");
        }
    }

最后说一下 id 相关。

<ViewStub
       android:inflatedId="@+id/inflatedId"
       android:id="@+id/viewStub"
       ... />

这里定义了 id 和 inflatedId。id 是通过 findViewById 来获取 ViewStub 的。inflatedId 则是获取被填充的布局,前提是调用了 ViewStub#setVisibility()或者 ViewStub#inflate()方法。inflatedId是在方法 inflateViewNoAdd() 中设置给被填充的布局的。

使用 ViewStub 需要注意的地方。

  • ViewStub 只能加载一次。这个可以从方法 replaceSelfWithView 看出来。因此 ViewStub 不适合需要按需隐藏的情况。
 private void replaceSelfWithView(View view, ViewGroup parent) {final int index = parent.indexOfChild(this);
        // 移除 ViewStub
        parent.removeViewInLayout(this);
        final ViewGroup.LayoutParams layoutParams = getLayoutParams();
        // 添加被填充的布局
        if (layoutParams != null) {parent.addView(view, index, layoutParams);
        } else {parent.addView(view, index);
        }
    }
  • ViewStub 不能嵌套 merge 标签

一般使用场景。

  • 程序运行期间,某个布局在加载后,就不会有变化,除非销毁该页面再重新加载。
  • 想要控制显示与隐藏的是一个布局文件,而非某个 view。
退出移动版