OliverLau 2019-06-21
ViewStub is a lightweight view with no dimension that doesn’t draw anything or participate in the layout. it's an invisible, zero-sized View that can be used to lazily inflate layout resources at runtime.
Android官方对ViewStub的解析:1.ViewStub一个不可见2.大小为0的试图. (下面会分析这两点实现) ViewStub好处:显示优酷视频加载评论列表的ListView,当没有数据或者网络加载失败时,如果inflate空列表的ListView会占用资源;当有数据时,才会inflate列表的ListView,延迟加载了布局.
<ViewStub android:id="@+id/list_stub" android:layout_width="match_parent" android:layout_height="match_parent" android:layout="@layout/stub_list" android:inflatedId="@+id/stub_list"/> stub_list.xml文件 <ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/list" android:layout_width="match_parent" android:layout_height="match_parent"/>
可以通过这两种方式inflate布局,第二种方式inflate布局不需要findViewById()找ListView.查看源码发现,可以通过设置setOnInflateListener()回调监听获取inflate的View.
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewStub, defStyleAttr, defStyleRes); // 解析android:inflatedId属性,其值是被inflate的View的ID mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID); // 解析android:layout属性,其值是被inflate的View布局 mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0); // 解析android:id属性,可以通过findViewByID找到该ViewStub mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID); a.recycle(); // 最重要的两点 setVisibility(GONE); // 设置不可见 setWillNotDraw(true); // 本View不会调用onDraw()方法绘制内容 }
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 设置试图大小为0 setMeasuredDimension(0, 0); }
public void setVisibility(int visibility) { if (mInflatedViewRef != null) { // 已经inflate()已经调用,直接获取该inflate的View控制可见性 View view = mInflatedViewRef.get(); if (view != null) { view.setVisibility(visibility); } else { throw new IllegalStateException("setVisibility called on un-referenced view"); } } else { // 如果没有调用过inflate()方法,并且设置了试图可见,就会调用inflate()加载布局 super.setVisibility(visibility); if (visibility == VISIBLE || visibility == INVISIBLE) { 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); // 设置mInflatedId给inflate的View if (mInflatedId != NO_ID) { view.setId(mInflatedId); } // 获取ViewStub在视图中的位置 final int index = parent.indexOfChild(this); // 从视图移除ViewStub parent.removeViewInLayout(this); final ViewGroup.LayoutParams layoutParams = getLayoutParams(); // 将inflate的试图加载父布局中,位置是被移除的ViewStub位置(也就是该ViewStub被替换成layoutView) 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"); }
}
// 设置回调监听 public void setOnInflateListener(OnInflateListener inflateListener) { mInflateListener = inflateListener; } public static interface OnInflateListener { // inflated:被inflate的View void onInflate(ViewStub stub, View inflated); }
以上三点注意的事项可以从源码中得知:
//注意事项第二点 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); }