最近在需求中使用到了 ViewStub 來動態加載佈局,因此總結一下 ViewStub 的使用和經常使用的佈局優化技巧。android
當界面中有些子佈局須要知足某些條件才展現時,一般咱們會採用將這個子佈局的 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
下面來總結下 ViewStub 的使用方式:.net
使用 layout 屬性引用填充的佈局文件,不能在 xml 中經過加子控件的方式添加,它是自閉合的(以/>
結尾)。3d
調用方式和普通的 View 同樣,在調用 inflate 方法時,返回被填充的 View,能夠對 View 中的控件再進行處理,若是不用處理時,能夠直接使用 setVisibility 方法。
inflate 方法只能被調用一次,再次調用時會拋出異常,由於在第一次 inflate 以後,ViewStub 被從視圖樹中移除,就獲取不到他的父佈局,而在 inflate 方法中須要獲取父佈局,因此會出錯。所以一般須要加 try-catch 進行處理,在 catch 中使用 setVisibility 方法來顯示填充的佈局。setVisibility 方法中使用了弱引用,能夠避免異常拋出。
能夠在 ViewStub 中重寫填充佈局的 layout_ 開頭的屬性,好比 width、height、layout_margin 等,非 layout 的屬性不能重寫,而且重寫 margin 等屬性,必需要先重寫 width 和 height 這兩個屬性,否則不會生效。
在 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 標籤須要注意如下幾點:
儘量讓佈局扁平,減小布局嵌套
優先使用 ConstraintLayout(首選)或 RelativeLayout 來編寫佈局,能夠有效地下降佈局的嵌套層次。使用 LinearLayout、FrameLayout 會有比較多的佈局嵌套。
避免使用 layout_weight屬性
layout_weight 屬性常常被使用在須要幾個控件幾等分父佈局時,是比較實用的一個屬性。可是對每一個設置了這個屬性的佈局、控件,系統都會執行兩次測量過程,在一些須要重複渲染布局的場合(RecyclerView中)會變得嚴重。所以要避免使用這個屬性,相同效果能夠改用 ConstraintLayout 或 RelativeLayout 來實現。