ViewStub 使用與佈局優化技巧

前言

最近在需求中使用到了 ViewStub 來動態加載佈局,因此總結一下 ViewStub 的使用和經常使用的佈局優化技巧。android

ViewStub

當界面中有些子佈局須要知足某些條件才展現時,一般咱們會採用將這個子佈局的 visibility 設爲 gone 或 invisible,隱藏這個佈局;當條件知足時,設爲 visible,讓其顯示。這樣操做比較簡單,可是性能表現通常。由於即便 View 隱藏了,仍是在佈局中,仍會被父容器繪製,仍會建立對象和實例化、設置屬性。bash

Android 中提供了一個輕量級的控件——ViewStub,它是一個不可見的,大小爲 0 的視圖,能夠在運行時動態加載佈局資源。當 ViewStub 被設爲 Visible 或者調用 inflate方法時,填充資源,而後 ViewStub 自己就被該佈局資源替換掉,ViewStub 被從 ViewTree 中移除。這個資源佈局在加載時會使用 ViewStub 中定義的 layout 屬性去覆蓋原來的對應屬性,好比 width、height、margin 等。能夠經過 inflatedId 屬性來定義或重寫被填充佈局的 id。ide

下面簡單寫了一個示例,來演示 ViewStub 的使用,代碼以下:佈局

Activity 的代碼:性能

class StubAcitivity : AppCompatActivity() {

    lateinit var viewStub: ViewStub
    var inflatedView: View? = null
    var stubImageView: ImageView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_stub)
        viewStub = findViewById(R.id.view_stub)
    }

    fun showViewStub(view: View) {
        try {
            inflatedView = viewStub.inflate()
        } catch (e: Exception) {
            viewStub.visibility = View.VISIBLE
        }

        stubImageView = inflatedView?.findViewById(R.id.stub_img) as ImageView
        stubImageView?.setImageResource(R.mipmap.ic_launcher)
    }

    fun changeViewStub(view: View) {
        stubImageView?.setImageResource(R.drawable.android10)
    }

    fun hideViewStub(view: View) {
        viewStub.visibility = View.INVISIBLE
    }
}
複製代碼

Activity 對應的佈局文件:優化

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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="顯示ViewStub"
            android:onClick="showViewStub"/>

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="更改ViewStub"
            android:onClick="changeViewStub"
            android:layout_marginLeft="20dp"
            android:layout_marginRight="20dp"/>

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="隱藏ViewStub"
            android:onClick="hideViewStub"/>
    </LinearLayout>

    <ViewStub
        android:id="@+id/view_stub"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout="@layout/layout_stub"/>
</LinearLayout>
複製代碼

ViewStub 引用的 layout 佈局文件:ui

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

    <ImageView
        android:id="@+id/stub_img"
        android:layout_width="300dp"
        android:layout_height="300dp"/>
</LinearLayout>
複製代碼

效果以下:spa

img1

下面來總結下 ViewStub 的使用方式:.net

  1. 使用 layout 屬性引用填充的佈局文件,不能在 xml 中經過加子控件的方式添加,它是自閉合的(以/>結尾)。3d

  2. 調用方式和普通的 View 同樣,在調用 inflate 方法時,返回被填充的 View,能夠對 View 中的控件再進行處理,若是不用處理時,能夠直接使用 setVisibility 方法。

  3. inflate 方法只能被調用一次,再次調用時會拋出異常,由於在第一次 inflate 以後,ViewStub 被從視圖樹中移除,就獲取不到他的父佈局,而在 inflate 方法中須要獲取父佈局,因此會出錯。所以一般須要加 try-catch 進行處理,在 catch 中使用 setVisibility 方法來顯示填充的佈局。setVisibility 方法中使用了弱引用,能夠避免異常拋出。

  4. 能夠在 ViewStub 中重寫填充佈局的 layout_ 開頭的屬性,好比 width、height、layout_margin 等,非 layout 的屬性不能重寫,而且重寫 margin 等屬性,必需要先重寫 width 和 height 這兩個屬性,否則不會生效。

  5. 在 ViewStub 所加載的佈局中不可使用 merge 標籤的,所以有可能致使加載出來的佈局存在着多餘的嵌套結構。

其餘的佈局優化技巧

使用 include 標籤進行佈局文件重用

include 標籤是很經常使用的一個標籤,它使用 layout 屬性引用另外一個佈局文件,將其添加到 include 標籤所在的父容器中。開發中能夠把一些公共的佈局提取成一個單獨的文件,而後使用 include 標籤引用,來減小重複編寫佈局的工做。一樣能夠在 include 標籤中對引用佈局的根節點屬性進行重寫。

<include
	android:layout_width = "match_parent"
	android:layout_height = "wrap_content"
	android:layout="@layout/title_bar"/>
複製代碼

使用 merge 標籤減小布局嵌套

咱們知道 Android 中 View 的佈局嵌套的越多,繪製時間就會越長,性能也越差,因此在編寫佈局時要儘可能減小布局的嵌套層次

include 標籤也有一個缺點,就是可能產生多餘的嵌套層次。好比:

activity.xml

<LinearLayout>
	<include layout="@layout/my_layout"/>
</LinearLayout>
複製代碼

my_layout.xml

<LinearLayout>
	<TextView/>
</LinearLayout>
複製代碼

那麼最終結果是:

<LinearLayout>
	<LinearLayout>
		<TextView/>
	</LinearLayout>
</LinearLayout>
複製代碼

其中第二個 LinearLayout 就是產生的多餘嵌套了。

這時候可使用 merge 標籤來配合 include 減小嵌套層次。

my_layout.xml

<merge>
	<TextView/>
</merge>
複製代碼

那麼結果是:

<LinearLayout>
	<TextView/>
</LinearLayout>
複製代碼

其實 merge 就是將其中的子控件直接填充到 include 所在的位置。這樣就不會存在多餘的嵌套層次了。

使用 merge 標籤須要注意如下幾點:

  1. merge 標籤必須是佈局文件的根節點
  2. merge 不是View,經過LayoutInflater.inflate來加載佈局時,第二個參數必須選擇一個父容器,第三個參數必須爲 true,必需要給 merge 標籤下的子View指定一個父節點。
  3. 對 merge 標籤設置的屬性無效

儘量讓佈局扁平,減小布局嵌套

優先使用 ConstraintLayout(首選)或 RelativeLayout 來編寫佈局,能夠有效地下降佈局的嵌套層次。使用 LinearLayout、FrameLayout 會有比較多的佈局嵌套。

避免使用 layout_weight屬性

layout_weight 屬性常常被使用在須要幾個控件幾等分父佈局時,是比較實用的一個屬性。可是對每一個設置了這個屬性的佈局、控件,系統都會執行兩次測量過程,在一些須要重複渲染布局的場合(RecyclerView中)會變得嚴重。所以要避免使用這個屬性,相同效果能夠改用 ConstraintLayout 或 RelativeLayout 來實現。

參考資料

ViewStub--使用介紹

Android最佳性能實踐(四)——佈局優化技巧

Android 佈局優化 Merge的使用

Android最佳實踐之UI篇

相關文章
相關標籤/搜索