? ? ? ?不合理的布局會(huì)使我們應(yīng)用程序UI性能變慢,客戶體檢會(huì)比較差。今天分享一些layout布局文件中的一些技巧,希望對(duì)大家寫出高質(zhì)量的布局文件能有一些幫助。
在開始之前先介紹一個(gè)能幫助我們優(yōu)化布局的一個(gè)工具。
? ? ? ?Hierarchy Viewer工具,提供了一個(gè)可視化界面顯示布局的層次結(jié)構(gòu)、以及查看每個(gè)界面measure,layout,draw所耗費(fèi)的時(shí)間。給我們優(yōu)化界面布局結(jié)構(gòu)提供了一個(gè)很好的參考。
一、ViewGroup的選擇
? ? ? ?所有的View都需要放在ViewGroup里面的顯示。有的時(shí)候相同的布局LinearLayout、RelativeLayout,F(xiàn)rameLayout,TableLayout,AbsoluteLayout里面的某幾種能實(shí)現(xiàn)。這個(gè)時(shí)候咱們應(yīng)該選用那個(gè)ViewGroup呢。
? ? ? ?ViewGroup的選擇有下面幾點(diǎn)
同樣的布局,層級(jí)深度小的一般優(yōu)于層級(jí)深度高的。(當(dāng)然這也不是絕對(duì)的)
同樣的布局,在不影響層級(jí)深度的情況下,使用LinearLayout和FrameLayout而不是RelativeLayout。(RelativeLayout會(huì)讓子View調(diào)用兩次onMeasure,LinearLayout一般會(huì)調(diào)用子View一次onMeasure,但是在有weight的時(shí)候,也會(huì)調(diào)用子View兩次onMeasure)
RelativeLayout子View盡量使用padding代替margin。
? ? ? ?RelativeLayout會(huì)讓子View調(diào)用兩次onMeasure,LinearLayout一般會(huì)調(diào)用子View一次onMeasure,但是在有weight的時(shí)候,也會(huì)調(diào)用子View兩次onMeasure。
? ? ? ?下面我們通過Hierarchy Viewer工具來對(duì)比下實(shí)現(xiàn)相同的布局,LinearLayout和RelativeLayout 測(cè)量耗時(shí)的對(duì)比。我們用LinearLayout和RelativeLayout來一個(gè)非常簡(jiǎn)單的布局上下兩個(gè)button的布局。看下耗時(shí)的對(duì)比。
LinearLayout 實(shí)現(xiàn)布局
線性布局.png
RelativeLayout 實(shí)現(xiàn)布局
相對(duì)布局.png
? ? ? ?界面的顯示時(shí)間,我們只需要關(guān)注Measure的時(shí)間(Layout和Draw時(shí)間相差不到我們認(rèn)為是一樣的)。相同的界面,哪個(gè)Measure時(shí)間端,性能就更好。實(shí)現(xiàn)相同的效果,Hierarchy Viewer顯示LinearLayout Measure時(shí)間 0.019 ms<RelativeLayout Measure時(shí)間 0.026 ms。LinearLayout 性能更優(yōu)。
二、include(布局重用)
? ? ? ?使用include標(biāo)簽,將另一個(gè)xml文件引入,作為布局的一部分。include的最大的作用是便于布局重用(比如我們所有的界面的標(biāo)題欄都是一樣的)。我們舉一個(gè)簡(jiǎn)單的例子來說明。
我們先寫一個(gè)layout_title.xml的xml文件用來做所有界面統(tǒng)一的標(biāo)題。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="42dp"
android:gravity="center"
android:background="@color/colorPrimary"
android:text="標(biāo)題"
android:textSize="18sp"
android:textColor="@android:color/white"/>
</RelativeLayout>
Activity對(duì)應(yīng)的布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
layout="@layout/layout_title"/>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:text="就一個(gè)布局"
android:textColor="@android:color/holo_red_dark"/>
</LinearLayout>
三、merge
? ? ? ?merge標(biāo)簽主要用于刪除多余的層級(jí)避免嵌套過多無用的布局,減少布局的深度。
? ? ? ?用一個(gè)非常簡(jiǎn)單的布局來說明merge標(biāo)簽的使用。就在界面上顯示一個(gè)TextView。
? ? ? ? 這里我們用Layout Inspector工具來替換Hierarchy Viewer 來查看頁面布局的層級(jí)結(jié)構(gòu)。Layout Inspector工具是Android Studio 替代 Hierarchy Viewer 的新方案
ViewGroup是FrameLayout,然后再里面放TextView
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:text="就一個(gè)布局"
android:textColor="@android:color/holo_red_dark"/>
</FrameLayout>
merge標(biāo)簽,然后再里面放TextView
<?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">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:text="就一個(gè)布局"
android:textColor="@android:color/holo_red_dark"/>
</merge>
? ? ? ?前后對(duì)比,發(fā)現(xiàn)使用merge的時(shí)候?qū)蛹?jí)確實(shí)少了一層(少了FrameLayout)。
3.1、merge使用場(chǎng)景
3.1.1、Activity layout文件的根視圖是FrameLayout的時(shí)候
? ? ? ?因?yàn)槲覀冊(cè)贏ctivity中setContentView()的content對(duì)應(yīng)的就是一個(gè)FrameLayout。所以當(dāng)我們布局文件的根視圖也是FrameLayout的時(shí)候完全可以使用merge標(biāo)簽來減少一層FrameLayout結(jié)構(gòu)。
3.1.2、merge標(biāo)簽配合自定義組合布局ViewGroup的使用
? ? ? ?自定義組合控件ViewGroup的時(shí)候,可以使用merge來減少一層ViewGroup。比如自定義繼承LinearLayout的組合控件,建議讓自定義View的layout布局文件根節(jié)點(diǎn)設(shè)置成merge。
3.1.3、merge標(biāo)簽配合include標(biāo)簽使用
? ? ? ?include標(biāo)簽使用的時(shí)候要注意merge標(biāo)簽的使用。可能某些情況下merge標(biāo)簽可以派上用場(chǎng)。接下來我們來改造一下文章前面include標(biāo)簽的實(shí)例。layout_title.xml文件我們完全可以使用merge標(biāo)簽來減少RelativeLayout的層級(jí)。
<?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="wrap_content">
<TextView
android:id="@+id/text_title"
android:layout_width="match_parent"
android:layout_height="42dp"
android:gravity="center"
android:background="@color/colorPrimary"
android:text="標(biāo)題"
android:textSize="18sp"
android:textColor="@android:color/white"/>
</merge>
并不是說include標(biāo)簽使用的時(shí)候都可以用merge標(biāo)簽來優(yōu)化布局哦。也是要根據(jù)實(shí)際情況來確定的。
3.2、merge使用的時(shí)候有幾點(diǎn)是要特別注意的
- merge必須放在布局文件的根節(jié)點(diǎn)上。
- merge并不是一個(gè)ViewGroup,也不是一個(gè)View,它相當(dāng)于聲明了一些視圖,等待被添加。
- merge標(biāo)簽被添加到A容器下,那么merge下的所有視圖將被添加到A容器下。
- 因?yàn)閙erge不是View,所以對(duì)merge標(biāo)簽設(shè)置的所有屬性都是無效的。
四、ViewStub
? ? ? ?ViewStub使用延遲加載的方式,避免資源的浪費(fèi),減少渲染時(shí)間,在需要的時(shí)候才加載View。即使將其放置于布局文件中,如果沒有進(jìn)行加載那就為空,不像其它控件一樣只要布局文件中聲明就會(huì)存在。比如有這么個(gè)情況,網(wǎng)絡(luò)請(qǐng)求的界面網(wǎng)絡(luò)成功顯示內(nèi)容信息,網(wǎng)絡(luò)請(qǐng)求失敗顯示失敗的界面。試想一下,如果網(wǎng)絡(luò)狀況良好,并不需要加載失敗頁面。這個(gè)時(shí)候使用ViewStub是一個(gè)非常好的選擇。
? ? ? ?ViewStub新增的屬性
- android:layout:設(shè)置ViewStub被inflate的布局控件。ViewStub的內(nèi)容布局。
- android:inflateId:重寫ViewStub的父布局控件的Id。
? ? ? ?ViewStub使用的時(shí)候我們一般使用inflate()或者setVisibility(View.VISIBLE)使ViewStub可見。但是咱們還得注意,當(dāng)setVisibility(int)或inflate()方法被調(diào)用之后,這個(gè)ViewStub在布局中將被使用指定的View(android:layout內(nèi)容)替換。而且inflate過一遍的ViewStub,如果被隱藏之后再次想要顯示,將不能使用inflate()方法,但是可以再次使用setVisibility(int)方法設(shè)置為可見。
? ? ? ?用一個(gè)簡(jiǎn)單的實(shí)例倆說明下ViewStub的使用。簡(jiǎn)單的實(shí)現(xiàn)點(diǎn)擊"顯示空數(shù)據(jù)"按鈕,就會(huì)顯示ViewStub對(duì)應(yīng)的布局。
activity_text.xml Activity布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
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:onClick="showEmpty"
android:text="顯示空數(shù)據(jù)" />
<ViewStub
android:id="@+id/view_stub_empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout="@layout/view_stub" />
</LinearLayout>
view_stub_empty.xml ViewStub內(nèi)容文件
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/hello_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="數(shù)據(jù)為空!" />
</FrameLayout>
Activity
public class TextActivity extends AppCompatActivity {
private ViewStub mViewStubEmpty;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_text);
mViewStubEmpty = findViewById(R.id.view_stub_empty);
}
public void showEmpty(View view) {
mViewStubEmpty.inflate();
}
}
ViewStub inflate()函數(shù)只能調(diào)用一次,重復(fù)調(diào)用會(huì)導(dǎo)致異常,這是因?yàn)閂iewStub只要加載過一次,其自身就會(huì)被移除,把并自身所包含的內(nèi)容全部傳給父布局。
ViewStub所替代的layout文件中不能使用merge標(biāo)簽。
以上就是對(duì)布局優(yōu)化做了一個(gè)比較簡(jiǎn)單的總結(jié)。