ViewStub源碼分析,viewstub源碼

來源:互聯網
上載者:User

ViewStub源碼分析,viewstub源碼

  ViewStub是一種特殊的View,Android官方給出的解釋是:一種不可見的(GONE)、size是0的佔位view,多用於運行時

消極式載入的,也就是說真正需要某個view的時候。在實際項目中,我發現它試用的情境大體有2種:

1. 某種只第一次需要顯示的view,比如某個介紹性的東西,比如使用者觸發了某些操作,給他彈出一個使用介紹,

這種介紹只出現一次,之後的操作中不會再出現,所以這種情況下可以先用ViewStub來佔個位,等介紹的view真正

需要的時候才inflate,一般情況下這種view的建立也比較expensive;

2. 某種直到運行時某一刻的時候才知道應該載入哪個view,比如你可能需要根據伺服器返回的某些資料來決定是顯示View 1

還是顯示View 2,這種情況下,也可以先用ViewStub佔位,直到可以做決定了在inflate相應的view;

為了接下來的分析方便,這裡我先貼一段ViewStub的xml代碼,如下:

<ViewStub     android:id="@+id/stub"    android:inflatedId="@+id/realView" // 可設定,不過一般意義不大    android:layout="@layout/my_real_view"    android:visibility="GONE" // really no need, don't write useless code    android:layout_width="match_parent"    android:layout_height="40dip" />

在代碼裡你同樣可以通過findViewById(R.id.stub)來找到此ViewStub,當真正的"my_real_view"布局被inflate之後,

ViewStub這個控制項就被從view階層中刪除了,取而代之的就是"my_real_view"代表的布局。注意下,這裡的所有

layout_width/height都是用來控制inflate之後的view的,而不是給ViewStub用的,這個可能是Android系統裡唯一的例外。

在我們的Java代碼裡使用的方式,官方推薦的做法是:

ViewStub stub = (ViewStub) findViewById(R.id.stub);View inflated = stub.inflate();

但這裡有一點需要留意的就是findViewById(R.id.stub),因為我們知道stub在第一次被inflate之後就會從view階層中

刪除,所以你應該要確保這樣的findViewById只會被調用一次(一般都是這樣的情況),但在有些你沒想到的case下

可能也會被調用多次。前幾天我們在項目裡就遇到了這個情況,出現了NPE,最終調查發現是stub的findViewById被又調用

了一次,導致了NPE(因為階層中已經找不到這個id了)。好的做法是比如把這種findViewById的放在onCreate類似

的只會被執行一次的地方,否則你就應該格外小心,自己加些保護處理。

  說了這麼多,接下來可以切入正題了,廢話不多說,從ctor開始上代碼:

    public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {        TypedArray a = context.obtainStyledAttributes(                attrs, com.android.internal.R.styleable.ViewStub, defStyleAttr, defStyleRes);        mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID); // 從layout檔案中讀取inflateId的值,如果有的話        mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0); // 同樣的,讀取真正的layout檔案        a.recycle();        a = context.obtainStyledAttributes(                attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);        mID = a.getResourceId(R.styleable.View_id, NO_ID); // 讀取View_id屬性        a.recycle();        initialize(context);    }    private void initialize(Context context) {        mContext = context;        setVisibility(GONE); // 看到這裡,你就明白了我前面xml檔案中提到的沒必要手動指定android:visibility="GONE"的原因        setWillNotDraw(true); // 因為ViewStub只是個臨時佔位的,它不做任何繪製操作,所以開啟這個標誌位以最佳化繪製過程,提高效能    }

  接下來,我們看幾個相關的比較簡單的方法,如下:

    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        setMeasuredDimension(0, 0); // 實現很簡單,寫死的大小為0,0    }    @Override    public void draw(Canvas canvas) { // 不做任何繪製    }    @Override    protected void dispatchDraw(Canvas canvas) { // 沒啥需要dispatch的    }    /**     * When visibility is set to {@link #VISIBLE} or {@link #INVISIBLE},     * {@link #inflate()} is invoked and this StubbedView is replaced in its parent     * by the inflated layout resource. After that calls to this function are passed     * through to the inflated view.     *     * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.     *     * @see #inflate()      */    @Override    @android.view.RemotableViewMethod    public void setVisibility(int visibility) { // setVisibility做了些特殊處理        if (mInflatedViewRef != null) { // 真正View的弱引用            View view = mInflatedViewRef.get();            if (view != null) {                view.setVisibility(visibility);            } else {                throw new IllegalStateException("setVisibility called on un-referenced view");            }        } else { // 當還沒inflate的時候,如果調了setVisibility方法,如果不是GONE的情況會自動在這種情況下調inflate的            super.setVisibility(visibility);            if (visibility == VISIBLE || visibility == INVISIBLE) {                inflate(); // 非GONE的情況,會多調用inflate()方法,所以你拿到ViewStub的執行個體後既可以主動調            }              // inflate()方法也可以通過setVisiblity(VISIBLE/INVISIBLE)來觸發對inflate的間接調用        }    }

  最後,我們來看看真正關鍵的方法inflate的實現,代碼如下:

    /**     * Inflates the layout resource identified by {@link #getLayoutResource()}     * and replaces this StubbedView in its parent by the inflated layout resource.     *     * @return The inflated layout resource.     *     */    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); // 注意這行,實際上還是調用了LayoutInflater的inflate方法,注意最後一個參數是false,所以這裡返回的                                // view就是真正layout檔案裡的root view而不是parent了。                if (mInflatedId != NO_ID) {                    view.setId(mInflatedId); // 如果layout檔案裡有給真正的view指定id就在這裡設定上                }                final int index = parent.indexOfChild(this);                parent.removeViewInLayout(this); // 找到ViewStub在parent中的位置並刪除ViewStub,看到了,在inflate的過程中                                                 // ViewStub真的是被刪掉了!!!                final ViewGroup.LayoutParams layoutParams = getLayoutParams(); // 擷取LayoutParams,就是你在xml檔案中寫的
// layout_xxx這樣的屬性,雖然實際上是屬於ViewStub的, if (layoutParams != null) { // 但下面緊接著就用這個LayoutParams來添加真正的view了 parent.addView(view, index, layoutParams); // 注意下添加的位置就是原先ViewStub待的位置,
// 所以等於就是完完全全替換了 } else { parent.addView(view, index); // 如果layoutParams是null的case } mInflatedViewRef = new WeakReference<View>(view); // 用view初始化弱引用 if (mInflateListener != null) { // 通過mInflateListener將inflate完成事件通知出去。。。 mInflateListener.onInflate(this, view); } return view; // 返回真正的view } else { // ViewStub必須指定真正的layout檔案!!! throw new IllegalArgumentException("ViewStub must have a valid layoutResource"); } } else { // ViewStub也必須有個parent view的!!! throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent"); } } /** * Specifies the inflate listener to be notified after this ViewStub successfully * inflated its layout resource. * * @param inflateListener The OnInflateListener to notify of successful inflation. * * @see android.view.ViewStub.OnInflateListener */ public void setOnInflateListener(OnInflateListener inflateListener) { mInflateListener = inflateListener; // 技術上要想響應inflate完成事件,有2種途徑,這裡通過設定listener是一種方式, } // 也可以通過更generic的方式:override真正view的onFinishInflate方法。 /** * Listener used to receive a notification after a ViewStub has successfully * inflated its layout resource. * * @see android.view.ViewStub#setOnInflateListener(android.view.ViewStub.OnInflateListener) */ public static interface OnInflateListener { /** * Invoked after a ViewStub successfully inflated its layout resource. * This method is invoked after the inflated view was added to the * hierarchy but before the layout pass. * * @param stub The ViewStub that initiated the inflation. * @param inflated The inflated View. */ void onInflate(ViewStub stub, View inflated); }}

  ViewStub的使用也被Android歸到提高效能裡面,之所以這麼說一方面是因為ViewStub有消極式載入(直到需要的時候才inflate

真正的view)、先佔位的作用,另外一方面也是由於對measure、layout、draw這3個關鍵過程的最佳化,因為ViewStub的這3個過程

很簡單,或者根本就是do nothing,所以效能很好。綜上,Android才建議我們用ViewStub來最佳化layout效能,當然也是真正合適使用

ViewStub的時機,而不是要你在所有的layout檔案中都這麼做,這個時機在開始的時候我也提到了,另外也可以參考下Android官方的文章:

http://developer.android.com/training/improving-layouts/loading-ondemand.html

http://developer.android.com/reference/android/view/ViewStub.html

 

P.S. 這篇文章本來應該是在上周末完成的,這周末去千島湖outing,在酒店裡實在無聊,發現還有篇未完成的文章就在這裡補充下,enjoy。。。 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.