淺析ViewStub
1. 特性
- ViewStub 是一個不可見,size為0的View,它通常用於在適當的時機去懶載入佈局。
- 一旦 ViewStub 設定為 Visible 或者 呼叫了 inflate() 方法,ViewStub 的佈局就會被載入。
- ViewStub 的佈局在載入後會直接替換它自己,所以 ViewStub 存在於 View hierarchy 直到它呼叫了setVisiblity() 或者inflate() 。
- ViewStub 設定的引數會直接傳遞給自身的佈局。
2. 示例
<ViewStub android:id="@+id/view_stub" android:layout_margin="10dp" android:inflatedId="@+id/tv_title" android:layout="@layout/tv_title_layout" android:layout_width="220dp" android:layout_height="50dp" />
- 如上面,我們可以使用id/view_stub 去尋找到 ViewStb, ViewStub在載入佈局後,ViewStub 本身消失了,其android:inflatedId="@+id/tv_title" 指定的就是佈局的ID,通過這個ID就可以找到載入後的佈局。
- 最後這個佈局的大小就是繼承於 ViewStub 的 120dp 40dp。
- 還有,android:inflatedId="@+id/tv_title" 這裡指定的ID必須要和android:layout="@layout/tv_title_layout" 裡面的ID對應起來。
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/tv_title" android:text="I'm inflate view." android:textColor="#000" android:textSize="15sp" android:layout_width="match_parent" android:layout_height="match_parent"> </TextView>
雖然,layout裡面佈局寫的 width 和 height 為match_parent ,但是不生效,只會繼承於 viewstub 指定的大小。
我們在程式碼裡面就可以這樣寫:
ViewStub viewStub = (ViewStub) findViewById(R.id.view_stub); if (viewStub!=null) { viewStub.inflate(); } TextView tvTitle = (TextView) findViewById(R.id.tv_title); tvTitle.setText("I'm inflate: " + System.currentTimeMillis());
3. 原始碼分析
變數
private int mInflatedId;//viewstub傳遞給佈局的id private int mLayoutResource;//viewstub的佈局 private WeakReference<View> mInflatedViewRef;
這裡有一個弱引用,是用來幹嘛的呢?我們先不管,往下看。
建構函式
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewStub, defStyleAttr, defStyleRes); mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID); mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0); mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID); a.recycle(); setVisibility(GONE); setWillNotDraw(true); }
很普通,直接從xml佈局中讀取相應的屬性,並且在建構函式中就設定為 GONE,接著:setWillNotDraw(true) ,設定一個標記,宣告這個View不做 onDraw 繪製。
/** * If this view doesn't do any drawing on its own, set this flag to * allow further optimizations. By default, this flag is not set on * View, but could be set on some View subclasses such as ViewGroup. * * Typically, if you override {@link #onDraw(android.graphics.Canvas)} * you should clear this flag. * * @param willNotDraw whether or not this View draw on its own */ public void setWillNotDraw(boolean willNotDraw) { setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); }
onMeasure,draw
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(0, 0); } @Override public void draw(Canvas canvas) { }
measure直接傳入0,說明ViewStub初始化就是一個0大小的View。
setVisibility & inflate
從上面知道,inflate 或者 setVisibility 都可以載入佈局,我們先看: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(); } } }
先判斷弱引用裡面是否有View,有的話就直接設定為可見,為空,就執行 inflate():
public View inflate() { final ViewParent viewParent = getParent(); if (viewParent != null && viewParent instanceof ViewGroup) { if (mLayoutResource != 0) { final ViewGroup parent = (ViewGroup) viewParent; //mInflater 可以從外部傳進來 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); } //獲取當前ViewStub在ViewGroup中的index final int index = parent.indexOfChild(this); //ViewStub替換成inflate後的View 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); //inflate回撥 if (mInflateListener != null) { mInflateListener.onInflate(this, view); } return view; } //省略異常 } //省略異常 }
- 先獲取到父容器,然後判斷佈局不為空情況下,進行載入佈局
- 使用的mInflate可以從外部通過方法public void setLayoutInflater(LayoutInflater inflater) 進來
- 載入佈局後設置好佈局的ID
- 接著獲取到當前 ViewStub 的位置 index,remove viewStub,add 新inflate 的 View 到 Index 位置上,也就是替換原來的 ViewStub
- 初始化弱引用,從這裡看到,弱引用包裹的是載入的 View
- 回撥器回撥
回撥
public static interface OnInflateListener { void onInflate(ViewStub stub, View inflated); }
在 inflate 的時候會進行回撥