Android 佈局優化--viewstub標籤

性能優化之一就是layout的優化,

as 常識:

佈局是否合理主要影響的是頁面測量時間的多少,我們知道一個頁面的顯示測量和繪製過程都是通過遞歸來完成的,多叉樹遍歷的時間與樹的高度h有關,其時間複雜度 O(h),如果層級太深,每增加一層則會增加更多的頁面顯示時間,所以佈局的合理性就顯得很重要。

那佈局優化有哪些方法呢,主要通過減少層級、減少測量和繪製時間、提高複用性三個方面入手。總結如下:

  • 減少層級。合理使用 RelativeLayout 和 LinerLayout,合理使用Merge。
  • 提高顯示速度。使用 ViewStub,它是一個看不見的、不佔佈局位置、佔用資源非常小的視圖對象。
  • 佈局複用。可以通過includ標籤來提高複用。
  • 儘可能少用wrap_content。wrap_content 會增加布局 measure 時計算成本,在已知寬高爲固定值時,不用wrap_content 。
  • 刪除控件中無用的屬性

看下viewstub。這個標籤吊的地方是需要顯示的時候在加載,其實就是把耗時點移動了,而並不是不耗時。

使用的話develop上有很清晰的講解:點擊打開鏈接

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

但是有個小細節,如果深扣或者說完美主義的話:

以develop上的例子:

 <ViewStub android:id="@+id/stub"
               android:inflatedId="@+id/subTree"
               android:layout="@layout/mySubTree"
               android:layout_width="120dip"
               android:layout_height="40dip" />

這裏layout也不要寫:

 <ViewStub android:id="@+id/stub"
               android:inflatedId="@+id/subTree"
               android:layout_width="120dip"
               android:layout_height="40dip" />

等到需要顯示的時候在set,這樣更符合優化的初衷。

viewStub.setLayoutResource(R.layout.mySubTree);
if (view == null) {
    view = viewStub.inflate();
}

其次就是使用注意點,一旦viewstub inflate了或者setvisibility了,則不能再次調用inflate否則會發生crash。


原理也很簡單:

1.首先看爲什麼layout用的時候在設置更好:如果一開始設置了,則會進行加載,不設置則直接0,這就是區別;

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);
}

2.看看這個viewstub如何做到需要的時候在顯示的,其他的view爲什麼直接加載了?

看到1中最後兩句setvisibility gone和willnotdraw true;

visibility都知道,gone不顯示不佔地方,那個後面這個呢,源碼如下,也就是設置flag,這樣在:

public void setWillNotDraw(boolean willNotDraw) {
    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}

解釋如下:也就是不會調用ondraw,從而優化

/**
     * 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.
     * {@hide}
     */
    static final int WILL_NOT_DRAW = 0x00000080;

不過,都知道,除了ondraw以爲還有onmeasure和layout呢,這個也是耗時點:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(0, 0);
}

都0了,很乾脆,所以也不用考慮了。

以上這兩點就是viewstub沒加載的原因。

接下來,看下,inflate或者setvisibility怎麼又顯示的?

根據基本知識,就知道,肯定有param和addview等操作了。

@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();
        }
    }
}

setvisibility也是通過inflate,所以一個意思,看inflate:

部分code:

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) {
            view.setId(mInflatedId);
        }

        final int index = parent.indexOfChild(this);
        parent.removeViewInLayout(this);

        final ViewGroup.LayoutParams layoutParams = getLayoutParams();
        if (layoutParams != null) {
            parent.addView(view, index, layoutParams);
        } else {
            parent.addView(view, index);
        }

1.必須有parent,否則沒法添加(合理,本來就是某個佈局裏的某個控件或者子佈局沒加載)

2.inflate layout,所以在inflate之前設置layout就ok。

factory.inflate(mLayoutResource, parent,

3.從parent中移除之前(0,0)大小的自己,在重新把解析完了帶param或者不帶param的自己add進去。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章