【建議收藏】17個(gè)XML布局小技巧

前言

我們開發(fā)時(shí)接觸最多的就是xml布局了,還記得我們寫Android的第一個(gè)Hello World嗎,就是通過activity_main.xml顯示出來的。

雖然xml寫的很多,而且也沒有什么技術(shù)難度,但是,這也往往是我們最容易忽略的地方,寫xml不難,寫出好的xml還是得下點(diǎn)功夫了。

什么算是好的xml布局呢,我認(rèn)為核心有兩點(diǎn),一個(gè)是提升開發(fā)效率,另一個(gè)是提升app性能。圍繞著這兩點(diǎn),我也精心整理出了17個(gè)xml布局小技巧,下面一起來看看都有哪些,你又掌握了幾個(gè)呢?

Space

官網(wǎng)是這么介紹的:

Space 是一個(gè)輕量級的 View 子類,可用于在通用布局中創(chuàng)建組件之間的間距。

為什么說是輕量級呢,是因?yàn)镾pace的draw方法是空的,也就是什么都不繪制,只有onMeasure方法測量寬高。

來看下源碼:

public final class Space extends View {

    /**
     * Draw nothing.
     *
     * @param canvas an unused parameter.
     */
    @Override
    public void draw(Canvas canvas) {
    }

    //...

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(
                getDefaultSize2(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize2(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
}

所以Space作用于組件之間的間距時(shí),繪制效率更高,特別是在需要?jiǎng)討B(tài)修改間距時(shí),這點(diǎn)尤為體現(xiàn)。

比如你要?jiǎng)討B(tài)修改組件的margin,如果用Space來當(dāng)間距,只需要修改Space的寬度或高度即可,因?yàn)闇p少了繪制流程,所以比重繪其他組件更高效。

使用起來也很簡單:

<Space
    android:id="@+id/space"
    android:layout_width="20dp"
    android:layout_height="20dp"/>

如果你想,Space完全可以替代margin,但是不一定能替代padding,因?yàn)閜adding是內(nèi)邊距,假如padding有背景色的話,就不能用Space代替了,因?yàn)镾pace的draw方法什么都不繪制的原因,所以也不會有背景色,除非背景色是在父view里設(shè)置的。

GuideLine

ConstraintLayout自2018年發(fā)布第一個(gè)正式版本以來,已經(jīng)4年多了,它通過扁平化的布局方式,有效的解決了層級嵌套的問題,不僅比RelativeLayout更靈活,而且性能上更佳,再配合上可視化工具拖拽編輯,效率上也有大大的提升,如果你還沒有用上,建議你一定要嘗試一下。

而在使用ConstraintLayout的過程中,我發(fā)現(xiàn)有些同學(xué)總是會忽略GuideLine,盡管ConstraintLayout已經(jīng)非常好用了,但是有些布局仍然顯得有些「笨拙」。而如果你能妙用GuideLine,你會發(fā)現(xiàn),布局越來越簡單,適配也越來越方便。

GuideLine是ConstraintLayout布局的輔助對象,僅用于布局定位使用,它被標(biāo)記了View.GONE,并不會顯示在設(shè)備上。

來看下源碼:

public class Guideline extends View {
    public Guideline(Context context) {
        super(context);
        super.setVisibility(View.GONE);
    }

    public Guideline(Context context, AttributeSet attrs) {
        super(context, attrs);
        super.setVisibility(View.GONE);
    }

    public Guideline(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        super.setVisibility(View.GONE);
    }

    public Guideline(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr);
        super.setVisibility(View.GONE);
    }

    //...
    @SuppressLint("MissingSuperCall")
    @Override
    public void draw(Canvas canvas) {

    }

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

標(biāo)記為View.GONE是這句super.setVisibility(View.GONE)設(shè)置的默認(rèn)值,不顯示還是因?yàn)閐raw方法為空,跟上面的Space同出一轍。

GuideLine可以通過3種不同的方式來輔助定位:

  • layout_constraintGuide_begin 指定距布局左側(cè)或頂部的固定距離
  • layout_constraintGuide_end 指定距布局右側(cè)或底部的固定距離
  • layout_constraintGuide_percent 指定布局寬度或高度的百分比

同時(shí)也可以指定不同的方向:

  • horizontal 垂直參考線
  • vertical 水平參考線

下面簡單演示一下效果:

  1. 箭頭所指處即創(chuàng)建GuideLine的地方,當(dāng)然也不止GuideLine,比如還有Barrier
  2. 第一個(gè)紅框里是水平參考線,70%定位,用百分比能很好的解決適配問題,而我們常規(guī)的做法是使用LinearLayout嵌套然后設(shè)置子view的weight,雖然嵌套一層不多,但那也是嵌套,就像懷孕一樣,你不能說只懷了一點(diǎn)點(diǎn)...
  3. 第二個(gè)紅框里是垂直參考線,距離左邊30dp,這種情況適合多個(gè)子view向一個(gè)目標(biāo)距離對齊,同樣減少了層級嵌套問題,省得再嵌套一層設(shè)置padding,或者多個(gè)子view分別設(shè)置margin。而右邊如果想要指定一個(gè)位置換行,可以了解一下Barrier~

xml代碼就不貼了,已上傳到Github,點(diǎn)擊查看

include

當(dāng)我們在寫一個(gè)復(fù)雜的頁面時(shí),xml代碼可能有幾百行甚至幾千行,閱讀起來總是很麻煩,如果又有很多的RelativeLayout嵌套的話,各個(gè)組件之間依賴關(guān)系錯(cuò)綜復(fù)雜,看起來更是頭大,這時(shí)候就可以考慮抽取一波,用總分總的模式分為header、content、footer,進(jìn)一步把內(nèi)容區(qū)抽成一個(gè)一個(gè)的獨(dú)立的子layout,然后使用include標(biāo)簽把它們分別引進(jìn)根布局,這就跟我們項(xiàng)目架構(gòu)設(shè)計(jì)一個(gè)意思,一個(gè)殼工程加n個(gè)子模塊。子layout只需要負(fù)責(zé)處理好自己內(nèi)部的布局,統(tǒng)籌交給父layout,這樣總體就比較清晰,想了解細(xì)節(jié)再去看子layout即可。

比如:

<include layout="@layout/content_scrolling"/>

content_scrolling即是我們抽出去的子layout。

tools:showIn

這個(gè)屬性一般是配合include標(biāo)簽使用的。當(dāng)我們把子layout抽出去之后,它的布局是相對獨(dú)立的效果,但是總歸要include到根布局的,如果能在子layout布局的時(shí)候看到它在父layout里面的效果,那就事半功倍了。

上面的content_scrolling.xml:

實(shí)際上布局只有一個(gè)TextView,但是在預(yù)覽視圖中還可以看到FloatingActionButton,這就是使用了tools:showIn屬性,當(dāng)子layout嵌入在父layout中時(shí),只需要使用tools:showIn在子layout的根布局指定父layout,就可以實(shí)時(shí)預(yù)覽在父layout中的效果了。

<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.yechaoa.materialdesign.activity.CollapsingToolbarActivity"
    tools:showIn="@layout/activity_collapsing_toolbar">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="@dimen/text_margin"
        android:text="@string/large_text"/>

</androidx.core.widget.NestedScrollView>

即:tools:showIn="@layout/activity_collapsing_toolbar"。

ViewStub

ViewStub是一個(gè)輕量級的條件視圖組件。在做類似頁面秒開這類性能優(yōu)化時(shí),是比較常見的延遲加載手段。

輕量級是因?yàn)閂iewStub跟Space一樣draw方法為空。

條件視圖的場景比如,當(dāng)我們需要根據(jù)條件判斷來顯示哪個(gè)view的時(shí)候,一般都會把每個(gè)場景的view都寫在頁面中,然后根據(jù)條件分別設(shè)置view的visibility,這樣做的缺點(diǎn)是,即使view是View.GONE,但是在頁面渲染加載的時(shí)候仍會實(shí)例化創(chuàng)建對象,并初始化它的屬性,很明顯這是浪費(fèi)資源的,所以這個(gè)時(shí)候用ViewStub是一個(gè)很好的優(yōu)化手段。

當(dāng)我們明確知道需要顯示哪個(gè)view的時(shí)候,通過ViewStub把實(shí)際視圖inflate進(jìn)來,這樣就避免了資源浪費(fèi)。

只有調(diào)用了ViewStub.inflate()的時(shí)候布局才會加載,才會創(chuàng)建對象實(shí)例化。

示例:

    <ViewStub
        android:id="@+id/stub_import"
        android:inflatedId="@+id/panel_import"
        android:layout="@layout/progress_overlay"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom" />

inflate:

    findViewById<View>(R.id.stub_import).visibility = View.VISIBLE
    // or
    val importPanel: View = findViewById<ViewStub>(R.id.stub_import).inflate()

tools:text

TextView是我們使用的最多的一個(gè)組件了,經(jīng)常有這樣的需求,“標(biāo)題顯示不下用...代替”,是不是很熟悉。

如果標(biāo)題是一個(gè)動(dòng)態(tài)數(shù)據(jù),默認(rèn)顯示app name,拿到數(shù)據(jù)后再更新。這種情況,一般都是在android:text里面加字符來調(diào)試,調(diào)試完了再改成默認(rèn)的app name,其實(shí)也不用這么麻煩,直接默認(rèn)app name,然后使用tools:text屬性就可以預(yù)覽字符超限的效果。

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/item_textView"
    android:layout_width="100dp"
    android:layout_height="wrap_content"
    android:maxLines="1"
    android:ellipsize="end"
    android:text="TextView"
    tools:text="TextViewTextView" />

默認(rèn)文案還是用android:text顯示,超限的效果用tools:text預(yù)覽即可,實(shí)際效果還是android:text,tools:text只是方便我們調(diào)試預(yù)覽,提高效率,減少編譯等待時(shí)間。

tools:visibility

這個(gè)屬性是用來預(yù)覽不顯示的View。

比如在“個(gè)人中心”頁面需要在昵稱后面給個(gè)文案提示“開通會員”,默認(rèn)不顯示,即android:visibility="gone",判斷不是會員后才顯示文案,但是在開發(fā)的過程中需要調(diào)試會員和非會員的兩種顯示效果,即可以通過tools:visibility="visible"來預(yù)覽顯示的效果,省得再編譯運(yùn)行造數(shù)據(jù)了,方便又提效。

代碼示例:

        <TextView
            tools:visibility="visible"
            android:visibility="gone"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@color/greenPrimary"
            android:gravity="center"
            android:padding="10dp"
            android:text="開通會員"
            android:textColor="@color/white" />

RecyclerView

RecyclerView也是我們使用非常高頻的一個(gè)組件了,一般會在xml中這么定義RecyclerView:

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycleView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

效果是這樣的:

這樣其實(shí)完全看不出RecyclerView在頁面中顯示的效果,只能每次編譯運(yùn)行看效果,而每次編譯運(yùn)行無疑會花費(fèi)我們很多寶貴的時(shí)間,下面就介紹幾個(gè)可以幫助大家提效的屬性。

tools:listitem

我們可以通過設(shè)置tools:listitem屬性來預(yù)覽item的顯示效果,tools:listitem屬性指定的是一個(gè)layout

tools:listitem="@layout/item_main"

效果:

tools:itemCount

預(yù)覽item在RecyclerView中顯示設(shè)置數(shù)量的效果,比如:

tools:itemCount="3"

即會顯示3個(gè)item的效果。

tools:listheader

tools:listheader="@layout/item_header"

效果同tools:listitem

tools:listfooter

效果同tools:listitem

tools:listfooter="@layout/item_footer"

app:layoutManager

上面RecyclerView的效果是默認(rèn)垂直方向的,我們都知道RecyclerView必須要設(shè)置一個(gè)layoutManager才可以顯示出來,我們通常會用代碼來設(shè)置,比如:

mBinding.recycleView.layoutManager = GridLayoutManager(this, 2)

實(shí)際上layoutManager也是可以在xml中通過app:layoutManager屬性來設(shè)置的,比如:

app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"

默認(rèn)的LinearLayoutManager是垂直方向的,如果我們想要改方向可以通過android:orientation屬性,比如:

android:orientation="horizontal"

這樣就可以在編寫xml的時(shí)候順手就加上了,既可以查看預(yù)覽效果,也避免了代碼忘記設(shè)置的尷尬情況。

app:spanCount

上面的示例中RecyclerView的layoutManager指定了LinearLayoutManager,我們還可以指定為GridLayoutManager,但是GridLayoutManager默認(rèn)的spanCount是1,如果我們需要設(shè)置spanCount為2,那該怎么預(yù)覽呢,這時(shí)候就用到了app:spanCount屬性,可以指定需要顯示的列數(shù)。

app:spanCount="2"

效果:

android:tint

著色器,這個(gè)屬性在之前的包體積優(yōu)化中有提到,可以減少圖片數(shù)量,從而減小包大小。

我們通常會用ImageView顯示一張圖片,比如原本是一個(gè)白色的返回icon,現(xiàn)在另一個(gè)地方要用黑色的了,就不需要使用黑白兩張圖了,而是使用tint來修改為黑色即可,當(dāng)然,也有局限,適合純色圖片。

效果:

示例:

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:orientation="horizontal">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@color/black"
                android:src="@mipmap/ic_back" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@color/red"
                android:src="@mipmap/ic_back"
                app:tint="@color/black" />

        </LinearLayout>

在appcompat的高版本中已經(jīng)改用app:tint代替。

代碼方式修改tint:

mBinding.imageView.imageTintList = ContextCompat.getColorStateList(this, R.color.greenPrimary)

除了tint還有backgroundTint,效果同理。

使用場景除了上面的示例外,還可以在點(diǎn)贊、收藏這類場景的顯示上使用。

android:divider

LinearLayout也是我們使用非常高頻的一個(gè)Layout,下面介紹兩個(gè)個(gè)少為人知的屬性。

相信很多人都用View寫過分割線的效果,類似這樣:

        <TextView />

        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#EEEEEE" />

        <TextView />

如上,當(dāng)有多個(gè)TextView之間需要添加分割線的時(shí)候,就只能一個(gè)一個(gè)復(fù)制,復(fù)制其實(shí)也沒什么,就是代碼看起來不優(yōu)雅。

其實(shí)有個(gè)比較優(yōu)雅的辦法,LinearLayout可以通過android:divider屬性添加分割線,結(jié)合android:showDividers屬性即可達(dá)到效果。

xml:

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="20dp"
        android:background="@drawable/shape_radius5_white"
        android:divider="@drawable/shape_divider_linear"
        android:orientation="vertical"
        android:showDividers="middle" >

        <TextView
            style="@style/MyTextView"
            android:text="刪除個(gè)人信息"
            app:drawableStartCompat="@mipmap/ic_helper" />

        <TextView
            style="@style/MyTextView"
            android:text="注銷賬戶"
            app:drawableStartCompat="@mipmap/ic_helper" />

        <TextView
            android:id="@+id/tv_about"
            style="@style/MyTextView"
            android:text="關(guān)于我們"
            app:drawableStartCompat="@mipmap/ic_helper" />

    </LinearLayout>

shape_divider_linear是分割線的樣式:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:left="50dp" >
        <shape android:shape="rectangle">
            <solid android:color="#F6F6F6" />
            <size android:height="1dp" />
        </shape>
    </item>
</layer-list>

效果:

showDividers有4個(gè)選項(xiàng):

  • middle 每兩個(gè)組件間顯示分隔線
  • beginning 開始處顯示分隔線
  • end 結(jié)尾處顯示分隔線
  • none 不顯示

其實(shí)舉一反三,除了分割線,View之間的間隔也可以這么實(shí)現(xiàn),省得每個(gè)子view都要寫margin。

android:animateLayoutChanges

animateLayoutChanges屬性是ViewGroup里面的,主要是在子view的添加和移除時(shí),添加一個(gè)默認(rèn)300ms的漸變動(dòng)畫。

代碼:

android:animateLayoutChanges="true"

效果:

默認(rèn)添加移除操作是比較生硬的,加上動(dòng)畫之后體驗(yàn)上會好很多。

當(dāng)然,如果你想修改默認(rèn)動(dòng)畫也是可以的。怎么修改?沒有比學(xué)習(xí)源碼更直接的了。

源碼:

case R.styleable.ViewGroup_animateLayoutChanges:
    boolean animateLayoutChanges = a.getBoolean(attr, false);
    if (animateLayoutChanges) {
        setLayoutTransition(new LayoutTransition());
    }
    break;

當(dāng)animateLayoutChanges屬性值為true時(shí),調(diào)用setLayoutTransition方法,并傳入一個(gè)默認(rèn)的LayoutTransition對象。

LayoutTransition對象用于構(gòu)造動(dòng)畫,跟一般的動(dòng)畫使用差不多,感興趣的可以看下官方文檔或者跟下源碼。

自定義LayoutTransition對象之后,調(diào)用ViewGroup.setLayoutTransition(LayoutTransition)即可。

android:foreground

android:foreground="?android:attr/selectableItemBackground"

在Android5.0以后,給View加上這個(gè)屬性之后,點(diǎn)擊時(shí)默認(rèn)會有一個(gè)水波紋的效果,一般可點(diǎn)擊的View默認(rèn)都有這個(gè)效果,比如Button,一般通常會在自定義的item view上加上這個(gè)屬性用來提升用戶體驗(yàn)。

最后

如上,本文一共介紹了17個(gè)在日常編寫xml的過程中對提升效率提升性能的屬性,如果你也有心得,歡迎評論補(bǔ)充。

如果本文對你有一丟丟幫助,也感謝點(diǎn)贊支持~

Github

github.com/yechaoa/Mat…

相關(guān)文檔

作者:yechaoa
鏈接:https://juejin.cn/post/7145861715798802462

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容