從源碼角度分析ViewStub 疑問與原理,源碼viewstub

來源:互聯網
上載者:User

從源碼角度分析ViewStub 疑問與原理,源碼viewstub

一、提出疑問
    ViewStub比較簡單,之前文章都提及到《Android 效能最佳化 三 布局最佳化ViewStub標籤的使用》,但是在使用過程中有一個疑惑,到底是ViewStub上設定的參數有效還是在其包括的layout中設定參數有效?如果不明白描述的問題,可以看下以下布局虛擬碼。

res/layout/main.xml<LinearLayout >    <ViewStub        android:id="@+id/viewstub"        android:layout_width="100dip"          android:layout_marginTop="100dip"        android:layout_height="wrap_content"        android:layout="@layout/sub_layout"        />   </LinearLayout>res/layout/sub_layout.xml<TextView     android:layout_width="50dip"     android:layout_marginTop="50dip"     android:layout_height="wrap_content"     android:text="ViewStub中包含的TextVeiw"/>

    上面的代碼中width最終效果是100dip還是50dip?marginTop是100dip還是50dip?帶著這個問題一起看下Android 5.0源碼看看ViewStub原理。
    為了便於把ViewStub與其infalte()載入出來的android:layout視圖做個區分,下文中針對前者統一命名“ViewStub視圖”,後者命名“被 載入視圖”,僅為了描述統一併不一定是專業名稱。
二、分析ViewStub源碼
    讓ViewStub有兩種方式一種是調用ViewStub.inflate() 另外一種是設定ViewStub.setVisibility(View.VISIBLE); 其實第二種方式依然是調用的infalte方法,可以看如下ViewStub源碼。
    @Override    @android.view.RemotableViewMethod    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();            }        }    }

ViewStub複寫了setVisibility方法,並在其中調用infalte方法,下面來看此方法源碼

public final class ViewStub extends View {     ......    public View inflate() {        final ViewParent viewParent = getParent(); // 1 為什麼可以直接擷取父視圖?          // ViewStub的父視圖必須是ViewGroup的子類        if (viewParent != null && viewParent instanceof ViewGroup) {            if (mLayoutResource != 0) { // ViewStub必須設定android:layout屬性                final ViewGroup parent = (ViewGroup) viewParent;                final LayoutInflater factory;                if (mInflater != null) {                    factory = mInflater;                } else {                    factory = LayoutInflater.from(mContext);                }                // 2 inflate被載入視圖                 final View view = factory.inflate(mLayoutResource, parent,                        false);                if (mInflatedId != NO_ID) {                    view.setId(mInflatedId);                }                // 從父視圖中擷取當前ViewStub在父視圖中的位置                final int index = parent.indexOfChild(this);                // 當前ViewStub也是個View僅僅只是用來佔位,所以先把佔位的ViewStub視圖刪除                parent.removeViewInLayout(this);                // 3 此處擷取的是ViewStub上面設定的參數                final ViewGroup.LayoutParams layoutParams = getLayoutParams();                if (layoutParams != null) {                    parent.addView(view, index, layoutParams);                } else {                    parent.addView(view, index);                }                // 目的是在複寫的setVisibility方法中使用                // 因為ViewStub.setVisibility操作的是被載入視圖並非當前ViewStub視圖                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");        }    }     ......}


下面說下源碼中列出的幾點。
 1. 為什麼可以直接擷取父視圖?
ViewStub 繼承自View其自身就是一個視圖,其調用getParent()可以從父類View上入手、
public class View {        public final ViewParent getParent() {        return mParent;    }    void assignParent(ViewParent parent) {        if (mParent == null) {            mParent = parent;        } else if (parent == null) {            mParent = null;        } else {            throw new RuntimeException("view " + this + " being added, but"                    + " it already has a parent");        }    }}


從View的源碼中擷取到,修改mParent參數的僅有assignParent方法且View中並未調用此方法,下面查看下其子類ViewGroup是否有調用。
public class ViewGroup {        public void addView(View child, int index, LayoutParams params) {            ......                 addViewInner(child, index, params, false);    }    private void addViewInner(View child, int index, LayoutParams params,            boolean preventRequestLayout) {                      ......                            // tell our children        if (preventRequestLayout) {            child.assignParent(this);        } else {            child.mParent = this;        }                   ......     }    }


從上面源碼可以看到在addView方法中會調用addViewInner,其中調用child.assignParent(this);,把自己所有子視圖mParent都設定成當前ViewGroup。從這一點也可以看出,ViewStub本身是一個View且載入的時候就已經添加到視圖樹中(View Tree)中,僅接著有另外一個問題既然頁面顯示的時候ViewStub已經被添加到介面上,為什麼有看不到ViewStub視圖呢?
疑問:為什麼ViewStub雖然是懶載入,但是其自身是一個視圖且展示介面就會添加到視圖樹中,為什麼看不到ViewStub?
public final class ViewStub extends View {    public ViewStub(Context context) {        initialize(context);    }          private void initialize(Context context) {        mContext = context;        setVisibility(GONE); // 初始化時把自己設定為隱藏        setWillNotDraw(true);    }         @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        setMeasuredDimension(0, 0); // 所有子視圖都設定為寬高為0    }    @Override    public void draw(Canvas canvas) { // 不對自身與子視圖進行繪製    }    @Override    protected void dispatchDraw(Canvas canvas) {    }}


從以上源碼可以看出ViewStub用盡所有辦法讓自己添加到視圖樹上是不顯示ViewStub自身。

2. inflate被載入視圖    再來看下載入android:layout視圖的源碼。final View view = factory.inflate(mLayoutResource, parent, false);    可以看到通過infalte方法記載的,其三個參數(int resource, ViewGroup root, boolean attachToRoot),分別是:mLayoutResource : 設定的android:layout的值                parent : 通過getParent()擷取即ViewStub的父視圖                  false : attachToRoot設定為false說明忽略androd:layout中根節點的layoutParams參數,即width=50dip與margin50dip
3. 視圖添加ViewStub.getLayoutParams參數此處源碼的是擷取ViewStub.getLayoutParams參數設定到anroid:layout載入的視圖上, 即width=100dip與marginTop=100dip生效。

三、總結開頭的疑問的答案,inflate出來的視圖width=100dip與marginTop=100dip而android:layout視圖中設定的width50dip和marginTop=50dip失效,等於沒有設定。
ViewStub的原理簡單描述是1. ViewStub本身是一個視圖,會被添加到介面上,之所以看不到是因為其源碼設定為隱藏與不繪製。2. 當調用infalte或者ViewStub.setVisibility(View.VISIBLE);時(兩個都使用infalte方法邏輯),先從父視圖上把當前ViewStub刪除,再把載入的android:layotu視圖添加上3. 把ViewStub layoutParams 添加到載入的android:layotu視圖上,而其根節點layoutParams 設定無效。4. ViewStub是指用來佔位的視圖,通過刪除自己並添加android:layout視圖達到懶載入效果



聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.