inflate()
的時候,佈局就會被加載(替換 ViewStub)。所以,ViewStub 一直存在於視圖層次結構中直到調用了 setVisibility(int)
或 inflate()
。先來看看構造方法:java
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewStub, defStyleAttr, defStyleRes); // 要被加載的佈局 Id mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID); // 要被加載的佈局 mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0); // ViewStub 的 Id mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID); a.recycle(); // 初始狀態爲 GONE setVisibility(GONE); // 設置爲不會繪製 setWillNotDraw(true); }
接下來就看看關鍵的方法,而後看看初始化狀態setVisibility方法。android
// 複寫了 setVisibility(int) 方法 @Override @android.view.RemotableViewMethod public void setVisibility(int visibility) { // private WeakReference<View> mInflatedViewRef; // mInflatedViewRef 是對佈局的弱引用 if (mInflatedViewRef != null) { // 若是不爲 null,就拿到懶加載的 View View view = mInflatedViewRef.get(); if (view != null) { // 而後就直接對 View 進行 setVisibility 操做 view.setVisibility(visibility); } else { // 若是爲 null,就拋出異常 throw new IllegalStateException("setVisibility called on un-referenced view"); } } else { super.setVisibility(visibility); // 以前說過,setVisibility(int) 也能夠進行加載佈局 if (visibility == VISIBLE || visibility == INVISIBLE) { // 由於在這裏調用了 inflate() inflate(); } } }
核心來了,平時用的時候,會常常調用到該方法。inflate() 是關鍵的加載實現,代碼以下所示:git
public View inflate() { // 獲取父視圖 final ViewParent viewParent = getParent(); if (viewParent != null && viewParent instanceof ViewGroup) { // 若是沒有指定佈局,就會拋出異常 if (mLayoutResource != 0) { // viewParent 需爲 ViewGroup final ViewGroup parent = (ViewGroup) viewParent; final LayoutInflater factory; if (mInflater != null) { factory = mInflater; } else { // 若是沒有指定 LayoutInflater factory = LayoutInflater.from(mContext); } // 獲取佈局 final View view = factory.inflate(mLayoutResource, parent, false); // 爲 view 設置 Id if (mInflatedId != NO_ID) { view.setId(mInflatedId); } // 計算出 ViewStub 在 parent 中的位置 final int index = parent.indexOfChild(this); // 把 ViewStub 從 parent 中移除 parent.removeViewInLayout(this); // 接下來就是把 view 加到 parent 的 index 位置中 final ViewGroup.LayoutParams layoutParams = getLayoutParams(); if (layoutParams != null) { // 若是 ViewStub 的 layoutParams 不爲空 // 就設置給 view parent.addView(view, index, layoutParams); } else { parent.addView(view, index); } // mInflatedViewRef 就是在這裏對 view 進行了弱引用 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"); } }
Inflate使用特色github
使用了弱引用管理對象的建立,代碼以下所示面試
@Override @android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync") 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 { } }
public View inflate() { final ViewParent viewParent = getParent(); if (viewParent != null && viewParent instanceof ViewGroup) { if (mLayoutResource != 0) { mInflatedViewRef = new WeakReference<>(view); return view; } else { throw new IllegalArgumentException("ViewStub must have a valid layoutResource"); } } }
首先先看一段源碼,以下所示:canvas
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(0, 0); } @Override public void draw(Canvas canvas) { } @Override protected void dispatchDraw(Canvas canvas) { }
有沒有以爲很不同凡響markdown
具體看一下setWillNotDraw(true)方法,代碼以下:網絡
public void setWillNotDraw(boolean willNotDraw) { setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); }
View中,對於WILL_NOT_DRAW是這樣定義的:app
/** * This view won't draw. {@link #onDraw(android.graphics.Canvas)} won't be * called and further optimizations will be performed. It is okay to have * this flag set and a background. Use with DRAW_MASK when calling setFlags.
*/ static final int WILL_NOT_DRAW = 0x00000080; ```
設置WILL_NOT_DRAW以後,onDraw()不會被調用,經過略過繪製的過程,優化了性能。在ViewGroup中,初始化時設置了WILL_NOT_DRAW,代碼以下:async
public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initViewGroup(); initFromAttributes(context, attrs, defStyleAttr, defStyleRes); } private void initViewGroup() { // ViewGroup doesn't draw by default if (!debugDraw()) { setFlags(WILL_NOT_DRAW, DRAW_MASK); } mGroupFlags |= FLAG_CLIP_CHILDREN; mGroupFlags |= FLAG_CLIP_TO_PADDING; mGroupFlags |= FLAG_ANIMATION_DONE; mGroupFlags |= FLAG_ANIMATION_CACHE; mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE; if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) { mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS; } setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS); mChildren = new View[ARRAY_INITIAL_CAPACITY]; mChildrenCount = 0; mPersistentDrawingCache = PERSISTENT_SCROLLING_CACHE; }
其實看一下源碼就很好理解:
public View inflate() { //獲取viewStub的父容器對象 final ViewParent viewParent = getParent(); if (viewParent != null && viewParent instanceof ViewGroup) { if (mLayoutResource != 0) { final ViewGroup parent = (ViewGroup) viewParent; //這裏是加載佈局,而且給它設置id //佈局的加載是經過LayoutInflater解析出來的 final View view = inflateViewNoAdd(parent); //這行代碼很重要,下面會將到 replaceSelfWithView(view, parent); //使用弱引用 mInflatedViewRef = new WeakReference<>(view); if (mInflateListener != null) { mInflateListener.onInflate(this, view); } return view; } else { //若是已經加載出來,再次inflate就會拋出異常呢 throw new IllegalArgumentException("ViewStub must have a valid layoutResource"); } } else { throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent"); } }
其實也能夠用一張圖來理解它,以下所示,摘自網絡