布局優化include, viewstub, merge

1 include

視圖引入,可以配合merge使用
重用布局

2.merge

<merge/>標簽在UI的結構優化中起著非常重要的作用,它可以刪減多余的層級,優化UI。<merge/>多用于替換FrameLayout或者當一個布局包含另一個時,<merge/>標簽消除視圖層次結構中多余的視圖組。例如你的主布局文件是垂直布局,引入了一個垂直布局的include,這是如果include布局使用的LinearLayout就沒意義了,使用的話反而減慢你的UI表現。這時可以使用<merge/>標簽優化。
減少視圖層級
步驟如下:
創建merge視圖

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="123" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="123" />
</merge>

視圖引入

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:layout_height="match_parent"
    tools:context="com.example.ticker.myapplication.MainActivity">
    <include layout="@layout/merge_layout"></include>
</LinearLayout>
3.ViewStub

當我們需要根據某個條件控制某個View的顯示或者隱藏的時候,通常是把可能用到的View都寫在布局上,然后設置可見性為View.GONE或View.InVisible ,之后在代碼中根據條件動態控制可見性。雖然操作簡單,但是耗費資源,因為即便該view不可見,仍會被父窗體繪制,仍會創建對象,仍會被實例化,仍會被設置屬性。
引入一個外部布局,與上面不同的是,viewstub引入的布局默認不會擴張,即既不會占用顯示也不會占用位置,從而在解析layout時節省cpu和內存。ViewStub是一個不可見的,大小為0的視圖,只有給他設置成了View.Visible或調用了它的inflate()之后才會填充布局資源。可以在運行過程中延時加載布局資源,inflate 方法只能被調用一次,因為調用后viewStub對象就被移除了視圖樹;

注:ViewStub目前有個缺陷就是還不支持 <merge /> 標簽。

使用步驟如下:
1、創建需要加載的布局

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:layout_height="match_parent">
    <Button
        android:layout_width="match_parent"
        android:text="button01"
        android:layout_height="wrap_content" />
    <Button
        android:layout_width="match_parent"
        android:text="button02"
        android:layout_height="wrap_content" />
</LinearLayout>

在需要引入的xml中引入

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.ticker.myapplication.MainActivity">

<Button
            android:id="@+id/btn_vs_showView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="顯示ViewStub"/>
    <ViewStub
        android:id="@+id/viewstub"
        android:layout_width="match_parent"
        android:layout="@layout/viewstub_layout"
        android:layout_height="match_parent" />
</RelativeLayout>

3、使用

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewStub viewStub = (ViewStub) findViewById(R.id.viewstub);
        View view = viewStub.inflate();
        Button btn_show = (Button) findViewById(R.id.btn_vs_showView);
    }


      @Override
      public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_vs_showView:

                //inflate 方法只能被調用一次,因為調用后viewStub對象就被移除了視圖樹;
                // 所以,如果此時再次點擊顯示按鈕,就會崩潰,錯誤信息:ViewStub must have a non-null ViewGroup viewParent;
                // 所以使用try catch ,當此處發現exception 的時候,在catch中使用setVisibility()重新顯示
                try {
                    View iv_vsContent = viewStub.inflate();     //inflate 方法只能被調用一次,
                    hintText = (TextView) iv_vsContent.findViewById(R.id.tv_vsContent);
                    //                    hintText.setText("沒有相關數據,請刷新");
                } catch (Exception e) {
                    viewStub.setVisibility(View.VISIBLE);
                } finally {
                    hintText.setText("沒有相關數據,請刷新");
                }
                break;
           
        }
    }
}
4、使用總結

1)ViewStub 引用布局時使用layout 屬性,取值@layout/xxxxx

2)InflateId 表示給被引用的布局的id。也就是被控制顯示或隱藏的布局的id.

3)如果不寫InflateId ,如果需要的話也可以直接在被引用的布局中給出id屬性

4)inflate() 方法只能被調用一次,如果再次調用會報異常信息 ViewStub must have a non-null ViewGroup viewParent。

這是因為,當ViewStub 調用inflate() 將其引用的 布局/view 展示出來之后,ViewStub本身就會從視圖樹中被移除,此時viewStub 就獲取不到他的 父布局, 而 inflate() 方法中,上來就需要獲取它的父布局,然后根據父布局是否為空再去執行具體的填充邏輯,如果為空就報上面的錯,所以,inflate() 之后如果還想再次顯示ViewStub 引用的布局/view 就需要 在調用inflate() 的時候try catch,當 catch 到異常的時候,調用setVisibility()設置viewStub 的View.Visible即可。ViewStub類中Inflate() 的具體邏輯如下:

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

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

            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的setVisibility()中也調用了inflate(),但是為什么多次調用setVisibility()不會導致崩潰呢?

ViewStub 的setVisibility() 方法中,會先判斷 WeakReference 類型的成員變量 mInflatedViewRef 是否為空。第一次調用setVisibility()的時候,mInflatedViewRef并沒有初始化,也就是說是null,那么這時候就會走inflate(),在inflate() 方法中給被填充起來的布局/view創建一個WeakReference弱引用,并賦值給mInflatedViewRef,從而完成mInflatedViewRef的初始化。當第二次走setVisibility() 的時候,mInflatedViewRef已經不再是null,就會調用 WeakReference 的父類Reference 中的get() 方法獲取該引用指向的實體對象,也就是說通過get() 拿到 被填充的view對象,然后再走View類的setVisibility()。ViewStub類中的setVisibility()具體實現如下:

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

7)ViewStub 的inflate() 只能被調用一次, 如果想控制/修改 被填充布局中的內容并重復顯示被填充的view,就用try 將viewStub.inflate() 以及修改內容的代碼包裹起來,并在catch 中setVisibility.

  1. 在xml 中定義ViewStub 節點時,內部不能包含其他節點,也就是說,ViewStub 是一個自閉合節點,如果一個布局/view如果想通過ViewStub顯示,只能定義在單獨的xml 文件中。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容