【建議收藏】17個XML佈局小技巧
前言
我們開發時接觸最多的就是 xml
佈局了,還記得我們寫Android的第一個 Hello World
嗎,就是通過activity_main.xml顯示出來的。
雖然xml寫的很多,而且也沒有什麼技術難度,但是,這也往往是我們最容易忽略的地方,寫xml不難,寫出好的xml還是得下點功夫了。
什麼算是好的xml佈局呢,我認為核心有兩點,一個是 提升開發效率
,另一個是 提升app效能
。圍繞著這兩點,我也精心整理出了 17
個xml佈局小技巧,下面一起來看看都有哪些,你又掌握了幾個呢?
官網是這麼介紹的:
Space 是一個輕量級的 View 子類,可用於在通用佈局中建立元件之間的間距。
為什麼說是輕量級呢,是因為Space的 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作用於元件之間的間距時,繪製效率更高,特別是在需要動態修改間距時,這點尤為體現。
比如你要動態修改元件的margin,如果用Space來當間距,只需要修改Space的寬度或高度即可,因為減少了繪製流程,所以比重繪其他元件更高效。
使用起來也很簡單:
<Space
android:id="@+id/space"
android:layout_width="20dp"
android:layout_height="20dp"/>
如果你想,Space完全可以替代margin,但是不一定能替代padding,因為padding是內邊距,假如padding有背景色的話,就不能用Space代替了,因為Space的draw方法什麼都不繪製的原因,所以也不會有背景色,除非背景色是在父view裡設定的。
GuideLine
ConstraintLayout自2018年釋出第一個正式版本以來,已經4年多了,它通過扁平化的佈局方式,有效的解決了層級巢狀的問題,不僅比RelativeLayout更靈活,而且效能上更佳,再配合上視覺化工具拖拽編輯,效率上也有大大的提升,如果你還沒有用上,建議你一定要嘗試一下。
而在使用ConstraintLayout的過程中,我發現有些同學總是會忽略 GuideLine
,儘管ConstraintLayout已經非常好用了,但是有些佈局仍然顯得有些「笨拙」。而如果你能妙用GuideLine,你會發現,佈局越來越簡單,適配也越來越方便。
GuideLine是ConstraintLayout佈局的輔助物件,僅用於佈局定位使用,它被標記了 View.GONE
,並不會顯示在裝置上。
來看下原始碼:
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);
}
//...
}
標記為View.GONE是這句 super.setVisibility(View.GONE)
設定的預設值,不顯示還是因為draw方法為空,跟上面的Space同出一轍。
GuideLine可以通過3種不同的方式來輔助定位:
-
layout_constraintGuide_begin 指定距佈局左側或頂部的固定距離
-
layout_constraintGuide_end 指定距佈局右側或底部的固定距離
-
layout_constraintGuide_percent 指定佈局寬度或高度的百分比
同時也可以指定不同的方向:
-
horizontal 垂直參考線
-
vertical 水平參考線
下面簡單演示一下效果:
-
箭頭所指處即建立GuideLine的地方,當然也不止GuideLine,比如還有Barrier
-
第一個紅框裡是水平參考線,70%定位,用百分比能很好的解決適配問題,而我們常規的做法是使用LinearLayout巢狀然後設定子view的weight,雖然巢狀一層不多,但那也是巢狀,就像懷孕一樣,你不能說只懷了一點點...
-
第二個紅框裡是垂直參考線,距離左邊30dp,這種情況適合多個子view向一個目標距離對齊,同樣減少了層級巢狀問題,省得再巢狀一層設定padding,或者多個子view分別設定margin。而右邊如果想要指定一個位置換行,可以瞭解一下Barrier~
xml程式碼就不貼了,已上傳到Github,點選檢視
include
當我們在寫一個複雜的頁面時,xml程式碼可能有幾百行甚至幾千行,閱讀起來總是很麻煩,如果又有很多的RelativeLayout巢狀的話,各個元件之間依賴關係錯綜複雜,看起來更是頭大,這時候就可以考慮抽取一波,用總分總的模式分為header、content、footer,進一步把內容區抽成一個一個的獨立的子layout,然後使用include標籤把它們分別引進根佈局,這就跟我們專案架構設計一個意思,一個殼工程加n個子模組。子layout只需要負責處理好自己內部的佈局,統籌交給父layout,這樣總體就比較清晰,想了解細節再去看子layout即可。
比如:
<include layout="@layout/content_scrolling"/>
content_scrolling即是我們抽出去的子layout。
tools:showIn
這個屬性一般是配合include標籤使用的。當我們把子layout抽出去之後,它的佈局是相對獨立的效果,但是總歸要include到根佈局的,如果能在子layout佈局的時候看到它在父layout裡面的效果,那就事半功倍了。
上面的content_scrolling.xml:
實際上佈局只有一個TextView,但是在預覽檢視中還可以看到FloatingActionButton,這就是使用了tools:showIn屬性,當子layout嵌入在父layout中時,只需要使用 tools:showIn
在子layout的根佈局指定父layout,就可以實時預覽在父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是一個輕量級的條件檢視元件。在做類似 頁面秒開
這類效能優化時,是比較常見的 延遲載入
手段。
輕量級是因為ViewStub跟Space一樣draw方法為空。
條件檢視的場景比如,當我們需要根據條件判斷來顯示哪個view的時候,一般都會把每個場景的view都寫在頁面中,然後根據條件分別設定view的visibility,這樣做的缺點是,即使view是View.GONE,但是在頁面渲染載入的時候仍會例項化建立物件,並初始化它的屬性,很明顯這是浪費資源的,所以這個時候用ViewStub是一個很好的優化手段。
當我們明確知道需要顯示哪個view的時候,通過ViewStub把實際檢視 inflate
進來,這樣就避免了資源浪費。
只有呼叫了ViewStub.inflate()的時候佈局才會載入,才會建立物件例項化。
示例:
<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是我們使用的最多的一個元件了,經常有這樣的需求,“標題顯示不下用...代替”,是不是很熟悉。
如果標題是一個動態資料,預設顯示app name,拿到資料後再更新。這種情況,一般都是在android:text裡面加字元來除錯,除錯完了再改成預設的app name,其實也不用這麼麻煩,直接預設app name,然後使用tools:text屬性就可以預覽字元超限的效果。
<?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" />
預設文案還是用android:text顯示,超限的效果用tools:text預覽即可,實際效果還是android:text,tools:text只是方便我們除錯預覽,提高效率,減少編譯等待時間。
tools:visibility
這個屬性是用來預覽不顯示的View。
比如在“個人中心”頁面需要在暱稱後面給個文案提示“開通會員”,預設不顯示,即android:visibility="gone",判斷不是會員後才顯示文案,但是在開發的過程中需要除錯會員和非會員的兩種顯示效果,即可以通過tools:visibility="visible"來預覽顯示的效果,省得再編譯執行造資料了,方便又提效。
程式碼示例:
<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也是我們使用非常高頻的一個元件了,一般會在xml中這麼定義RecyclerView:
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycleView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
效果是這樣的:
這樣其實完全看不出RecyclerView在頁面中顯示的效果,只能每次編譯執行看效果,而每次編譯執行無疑會花費我們很多寶貴的時間,下面就介紹幾個可以幫助大家 提效
的屬性。
tools:listitem
我們可以通過設定tools:listitem屬性來預覽item的顯示效果,tools:listitem屬性指定的是一個layout
tools:listitem="@layout/item_main"
效果:
tools:itemCount
預覽item在RecyclerView中顯示設定數量的效果,比如:
tools:itemCount="3"
即會顯示3個item的效果。
tools:listheader
tools:listheader="@layout/item_header"
效果同tools:listitem
tools:listfooter
效果同tools:listitem
tools:listfooter="@layout/item_footer"
app:layoutManager
上面RecyclerView的效果是預設垂直方向的,我們都知道RecyclerView必須要設定一個layoutManager才可以顯示出來,我們通常會用程式碼來設定,比如:
mBinding.recycleView.layoutManager = GridLayoutManager(this, 2)
實際上layoutManager也是可以在xml中通過 app:layoutManager
屬性來設定的,比如:
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
預設的LinearLayoutManager是垂直方向的,如果我們想要改方向可以通過 android:orientation
屬性,比如:
android:orientation="horizontal"
這樣就可以在編寫xml的時候順手就加上了,既可以檢視預覽效果,也避免了程式碼忘記設定的尷尬情況。
app:spanCount
上面的示例中RecyclerView的layoutManager指定了LinearLayoutManager,我們還可以指定為GridLayoutManager,但是GridLayoutManager預設的spanCount是1,如果我們需要設定spanCount為2,那該怎麼預覽呢,這時候就用到了 app:spanCount
屬性,可以指定需要顯示的列數。
app:spanCount="2"
效果:
android:tint
著色器,這個屬性在之前的包體積優化中有提到,可以減少圖片數量,從而減小 包大小
。
我們通常會用ImageView顯示一張圖片,比如原本是一個白色的返回icon,現在另一個地方要用黑色的了,就不需要使用黑白兩張圖了,而是使用tint來修改為黑色即可,當然,也有侷限,適合純色圖片。
效果:
示例:
<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的高版本中已經改用 app:tint
代替。
程式碼方式修改tint:
mBinding.imageView.imageTintList = ContextCompat.getColorStateList(this, R.color.greenPrimary)
除了tint還有backgroundTint,效果同理。
使用場景除了上面的示例外,還可以在點贊、收藏這類場景的顯示上使用。
android:divider
LinearLayout也是我們使用非常高頻的一個Layout,下面介紹兩個個少為人知的屬性。
相信很多人都用View寫過分割線的效果,類似這樣:
<TextView />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#EEEEEE" />
<TextView />
如上,當有多個TextView之間需要新增分割線的時候,就只能一個一個複製,複製其實也沒什麼,就是程式碼看起來不優雅。
其實有個比較優雅的辦法,LinearLayout可以通過 android:divider
屬性新增分割線,結合 android:showDividers
屬性即可達到效果。
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="刪除個人資訊"
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="關於我們"
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個選項:
-
middle 每兩個元件間顯示分隔線
-
beginning 開始處顯示分隔線
-
end 結尾處顯示分隔線
-
none 不顯示
其實舉一反三,除了分割線,View之間的間隔也可以這麼實現,省得每個子view都要寫margin。
android:animateLayoutChanges
animateLayoutChanges
屬性是ViewGroup裡面的,主要是在子view的新增和移除時,新增一個預設300ms的漸變動畫。
程式碼:
android:animateLayoutChanges="true"
效果:
預設新增移除操作是比較生硬的,加上動畫之後體驗上會好很多。
當然,如果你想修改預設動畫也是可以的。怎麼修改?沒有比學習原始碼更直接的了。
原始碼:
case R.styleable.ViewGroup_animateLayoutChanges:
boolean animateLayoutChanges = a.getBoolean(attr, false);
if (animateLayoutChanges) {
setLayoutTransition(new LayoutTransition());
}
break;
當animateLayoutChanges屬性值為true時,呼叫 setLayoutTransition
方法,並傳入一個預設的 LayoutTransition
物件。
LayoutTransition物件用於構造動畫,跟一般的動畫使用差不多,感興趣的可以看下官方文件或者跟下原始碼。
自定義LayoutTransition物件之後,呼叫 ViewGroup.setLayoutTransition(LayoutTransition)
即可。
android:foreground
android:foreground="?android:attr/selectableItemBackground"
在Android5.0以後,給View加上這個屬性之後,點選時預設會有一個水波紋的效果,一般可點選的View預設都有這個效果,比如Button,一般通常會在自定義的item view上加上這個屬性用來提升使用者體驗。
最後
如上,本文一共介紹了17個在日常編寫xml的過程中對 提升效率
和 提升效能
的屬性,如果你也有心得,歡迎評論補充。
如果本文對你有一丟丟幫助,也感謝點贊支援~
Github
https://github.com/yechaoa/MaterialDesign
作者:yechaoa
連結:https://juejin.cn/post/7145861715798802462
關注我獲取更多知識或者投稿
- 【建議收藏】17個XML佈局小技巧
- Kotlin擴充套件方法進化之Context Receiver
- 網路請求元件封裝
- Flutter 繪製探索 | 箭頭端點的設計
- 在網頁上除錯手機上的APP
- Android應用安全解決方案
- Android應用安全開發之元件安全淺談
- Android 元件化架構設計從原理到實戰
- 涉作業系統技術領域,華為公開安卓應用程式遷移相關專利
- 避坑!!webview如何載入pdf ?
- Hook AMS APT實現集中式登入框架
- Retrofit是如何支援協程的
- 自定義雙向繫結框架-只需一個註解,簡單實用
- Android 10、11 儲存完全適配
- Java註解和註解解析器深耕,架構師必會
- 寫個更牛逼的Transform | Plugin 進階教程
- Android 騰訊 Matrix 原理分析(一):Matrix 概覽
- ART視角 | 如何自動回收native記憶體
- Android Gradle 多渠道打包
- Iterator 這些點你GET到了嗎?