引言:一个可用于性能优化的控件。
时间:2017年09月21日
作者:JustDo23
Github:https://github.com/JustDo23
官网:https://developer.android.com/reference/android/view/ViewStub.html
01. 简介
A ViewStub is an invisible, zero-sized View that can be used to lazily inflate layout resources at runtime.
控件 ViewStub
是一个不可见的,零尺寸的 View
它可以在运行时进行延迟加载。
When a ViewStub is made
visible
, or wheninflate()
is invoked, the layout resource is inflated. The ViewStub then replaces itself in its parent with the inflated View or Views.
当 ViewStub
的 setVisibility(int)
方法或者 inflate()
方法被调用,它会加载被指定的布局并在父布局中将自己替换为加载的布局。
Therefore, the ViewStub exists in the view hierarchy until
setVisibility(int)
orinflate()
is invoked.
替换后,控件 ViewStub
会从布局树中移除。
The inflated View is added to the ViewStub's parent with the ViewStub's layout parameters.
控件 ViewStub
的布局属性会传递给被加载的布局。
- 因此,不是必须显示的布局使用
ViewStub
代替后减少界面首次加载时资源消耗,提升最初加载速度。
02. 用法
-
布局
<ViewStub android:id="@+id/viewStub" android:layout_width="match_parent" android:layout_height="50dp" android:layout_marginTop="10dp" android:inflatedId="@+id/titleBar" android:layout="@layout/just_title" />
-
android:layout
指定被加载替换的布局 -
android:inflatedId
指定被加载替换的布局的 id
-
-
加载
viewStub = (ViewStub) findViewById(R.id.viewStub); View inflated = stub.inflate();
- 官方推荐加载首选方法
- 调用
inflate()
方法后布局被加载替换同时返回布局对象。避免了使用findViewById()
方法。 - 该
inflate()
方法只能调用一次,调用被移除而没有了父布局。第二次调用会抛出异常ViewStub must have a non-null ViewGroup viewParent
。
03. 示例
-
主界面布局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ViewStub android:id="@+id/viewStub" android:layout_width="match_parent" android:layout_height="50dp" android:layout_marginTop="10dp" android:inflatedId="@+id/titleBar" android:layout="@layout/just_title" /> <Button android:id="@+id/bt_show" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="80dp" android:text="Show" android:textAllCaps="false" /> <Button android:id="@+id/bt_hide" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/bt_show" android:layout_marginTop="10dp" android:text="Hide" android:textAllCaps="false" /> </RelativeLayout>
-
被替换布局
just_title.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="50dp" android:background="@color/colorPrimary"> <TextView android:id="@+id/tv_show_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:ellipsize="end" android:singleLine="true" android:textColor="@android:color/white" android:textSize="20sp" tools:text="Title" /> </RelativeLayout>
-
界面加载
public class ViewStubActivity extends AppCompatActivity implements View.OnClickListener { private ViewStub viewStub;// 占位控件 private Button bt_show;// 显示按钮 private Button bt_hide;// 隐藏按钮 private TextView tv_show_title;// 标题 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_view_stub); viewStub = (ViewStub) findViewById(R.id.viewStub);// 寻找控件 bt_show = (Button) findViewById(R.id.bt_show); bt_hide = (Button) findViewById(R.id.bt_hide); bt_show.setOnClickListener(this); bt_hide.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.bt_show:// 显示 try { View titleBar = viewStub.inflate();// 第二次加载会抛出异常 tv_show_title = (TextView) titleBar.findViewById(R.id.tv_show_title); tv_show_title.setText("Title"); } catch (Exception e) { viewStub.setVisibility(View.VISIBLE); } break; case R.id.bt_hide:// 隐藏 viewStub.setVisibility(View.GONE); break; } } }
-
隐藏
-
显示
-
更换加载方式
@Override public void onClick(View v) { switch (v.getId()) { case R.id.bt_show:// 显示 viewStub.setVisibility(View.VISIBLE);// 方式二 if (tv_show_title == null) { tv_show_title = (TextView) findViewById(R.id.tv_show_title); tv_show_title.setText("Title"); } break; } }
04. 监听
-
加载监听回调
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_view_stub); viewStub = (ViewStub) findViewById(R.id.viewStub);// 寻找控件 // 设置加载监听回调,成功加载后回调[只会回调一次] viewStub.setOnInflateListener(new ViewStub.OnInflateListener() { /** * Listener used to receive a notification after a ViewStub has successfully inflated its layout resource. * * @param stub ViewStub 对象 * @param inflated 被加载填充的布局 */ @Override public void onInflate(ViewStub stub, View inflated) { tv_show_title = (TextView) inflated.findViewById(R.id.tv_show_title); tv_show_title.setText("ShowTitle"); } }); }
按需使用
05. 源码
-
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();// 调用填充方法 } } }
-
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) {// 被指定的布局的 ID view.setId(mInflatedId);// 即 android:inflatedId 指定的新 ID } final int index = parent.indexOfChild(this);// 获取位置 parent.removeViewInLayout(this);// 移除 ViewStub 自己 final ViewGroup.LayoutParams layoutParams = getLayoutParams();// ViewStub 指定的布局参数 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"); } }
06. 注意
-
ViewStub
支持使用<include>
标签的布局。 -
ViewStub
不支持使用<merge>
标签的布局。