引言:一個(gè)可用于性能優(yōu)化的控件垛膝。
時(shí)間:2017年09月21日
作者:JustDo23
Github:https://github.com/JustDo23
官網(wǎng):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
是一個(gè)不可見的,零尺寸的 View
它可以在運(yùn)行時(shí)進(jìn)行延遲加載。
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.
當(dāng) ViewStub
的 setVisibility(int)
方法或者 inflate()
方法被調(diào)用,它會(huì)加載被指定的布局并在父布局中將自己替換為加載的布局。
Therefore, the ViewStub exists in the view hierarchy until
setVisibility(int)
orinflate()
is invoked.
替換后,控件 ViewStub
會(huì)從布局樹中移除胳赌。
The inflated View is added to the ViewStub's parent with the ViewStub's layout parameters.
控件 ViewStub
的布局屬性會(huì)傳遞給被加載的布局。
- 因此匙隔,不是必須顯示的布局使用
ViewStub
代替后減少界面首次加載時(shí)資源消耗疑苫,提升最初加載速度。
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();
- 官方推薦加載首選方法
- 調(diào)用
inflate()
方法后布局被加載替換同時(shí)返回布局對(duì)象纷责。避免了使用findViewById()
方法捍掺。 - 該
inflate()
方法只能調(diào)用一次,調(diào)用被移除而沒有了父布局再膳。第二次調(diào)用會(huì)拋出異常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;// 標(biāo)題 @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();// 第二次加載會(huì)拋出異常 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. 監(jiān)聽
-
加載監(jiān)聽回調(diào)
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_view_stub); viewStub = (ViewStub) findViewById(R.id.viewStub);// 尋找控件 // 設(shè)置加載監(jiān)聽回調(diào),成功加載后回調(diào)[只會(huì)回調(diào)一次] viewStub.setOnInflateListener(new ViewStub.OnInflateListener() { /** * Listener used to receive a notification after a ViewStub has successfully inflated its layout resource. * * @param stub ViewStub 對(duì)象 * @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 {// 第一次會(huì)為空 super.setVisibility(visibility); if (visibility == VISIBLE || visibility == INVISIBLE) { inflate();// 調(diào)用填充方法 } } }
-
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 指定的布局參數(shù) if (layoutParams != null) { parent.addView(view, index, layoutParams);// 指定位置和參數(shù)填充 } else { parent.addView(view, index); } mInflatedViewRef = new WeakReference<View>(view);// 弱引用 if (mInflateListener != null) {// 監(jiān)聽器不空 mInflateListener.onInflate(this, view);// 監(jiān)聽回調(diào)熙参。所以只回調(diào)一次。 } 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>
標(biāo)簽的布局麦备。 -
ViewStub
不支持使用<merge>
標(biāo)簽的布局孽椰。