屏幕適配一直是困擾 Android 開發工程師的一大問題,但是隨着近幾年各種屏幕適配方案的誕生,以及谷歌各種適配控件的推出,屏幕適配也顯得越來越容易,這節課我們就來總結一下關於屏幕適配的那些技巧。
ConstraintLayout
很多工程師不太喜歡使用 ConstraintLayout,感覺 ConstraintLayout 的使用很煩瑣,要設置各種上下左右的約束條件。但是請相信我,前期你在代碼里付出的越多,后期你需要解決的 bug 就越少!ConstraintLayout 是我個人最喜歡的 Android 控件之一,它的前身是 PercentLayout(百分比布局),當年 PercentLayout 被推出時也是火爆一時,但是它只延續了很短的一段時間就被 ConstraintLayout 替代了。
ConstraintLayout 的常見屬性有以下幾個:

說明:
紅框 1 中屬性相當於 RelativeLayout 的 layout_align 相關屬性,能夠確定各個 View 之間邊對齊特征。
紅框 2 中的屬性相當於 RelativeLayout 的 layout_to 相關屬性,能夠確定各個 View 之間的相對位置。
通過這幾個屬性,基本能夠確立 View 的相對位置,並且還能夠實現其他 View 容器較難實現的效果。比如有兩個 Button 分別是 Button1 和 Button2,需求是將 Button1 置位於屏幕中間,並且始終覆蓋 Button2 的左上半角,UI 效果如下:

上述效果就可以使用以下代碼實現:

ConstraintLayout 還有幾個其他特殊屬性,通過它們可以幫助我們更好地做出適配。
Bias
ConstraintLayout 提供了水平和垂直方向的 bias 屬性,這個屬性的取值范圍是 0~1。主要作用是確立 View 在水平方向或者垂直方向的位置百分比。比如以下示例代碼:

圖中的 horizontal_bias 和 vertical_bias 分別指定 TextView 顯示在水平方向的 30% 位置和垂直方向上 50% 的位置,最終顯示效果如下:

weight
LinearLayout 可以很方便地實現將多個 UI 控件按照某一方向進行排列,並且設置一定的權重規則。而 ConstraintLayout 也能夠實現類似的效果。
以下代碼可以使 3 個 TextView 橫向依次按照相等的權重來排列。

顯示效果如下:

ConstraintLayout 還提供了 chain 屬性來設置不同的均分策略,具體有以下幾種屬性值:
spread
它將平分剩余空間,讓 ConstraintLayout 內部 Views 平分占用剩余空間,spread 也是默認屬性,顯示效果就如上文中的顯示效果。
- spread_inside ,它會將兩邊的最邊緣的兩個 View 拉向父組件邊緣,然后讓剩余的 Views 在剩余的空間內平分間隙布局,代碼及顯示效果如下:
app:layout_constraintHorizontal_chainStyle="spread_inside"


- packed ,它將所有 Views 集中到一起不分配多余的空間(margin 除外),然后將整個組件顯示在可用的剩余位置居中,代碼及效果如下:
app:layout_constraintHorizontal_chainStyle="packed"

在 chain 的基礎上,還可以再加上 bias 屬性使其在某百分比位置上按照權重排列,比如在上述 packed chainstyle 下,我再在 TextView t1 中添加如下屬性:
app:layout_constraintHorizontal_bias=".75"
最終顯示效果如下:

注意:
使用 ConstraintLayout 時,需要特別主要 UI 控件的可見屬性。因為 ConstraintLayout 內部控件的 visibility,設置為 GONE 和 INVISIBLE 對其他控件的約束是不一樣的。
多 dimens 基於 dp 的適配方案
在 ConstraintLayout 的基礎上,我們還可以在 res 文件夾中創建多套 values 文件夾,如下所示:

圖中 values- 后的 sw 指的是 smallest width,也就是最小寬度。Android 系統在運行時會自動識別屏幕可用的最小寬度,然后根據識別的結果去資源文件中查找相對應的資源文件中的屬性值。比如有一個 360dpi 的手機設備在運行 App 時,會自動到 values-sw360dp 文件夾中尋找對應的值。手寫每個 values 文件很麻煩,可以借助於工具或gradle plugin一鍵生成,具體可以參考這個鏈接中的介紹: android屏幕適配,自動生成不同的dimens.xml詳解
這種方式有很好的容錯機制,比如如果一個手機的最小寬度是 350dp,Android 系統如果在 res 中沒有找到 values-sw350dp 文件夾,也不會直接使用默認的 values 文件夾中的值,而是向下依次查找最接近的最小寬度文件夾,比如上圖中離 350dp 最近的是 values-sw320dp 中的值,這個值雖然不是 100% 精確,但是效果也不會相差太遠。
通過上文介紹的 ConstraintLayout + 多 dimens 適配方案,基本能夠將 UI 布局適配到所有的機型。在此基礎上,再針對個別 UI 控件進行適配就基本完美了。
UI 控件適配
在 Android App 中文本 + 圖片內容占據了一個 App 顯示 UI 的絕大部分,雖然會夾雜 RecyclerView、ViewPager、ScrollView 等嵌套視圖,但是最終在嵌套視圖內部包含的還是文本內容 + 圖片內容,因此這兩者的適配是我們重點關注的對象。
文字 TextView
對於 TextView 的寬高,建議盡量使用 wrap_content 自適應,因為一旦使用具體值進行限定,我們無法保證它不會在某些手機上被 cut 掉。舉一個血淋淋的例子:在搜索界面有一個 “清空” 按鈕,寬度設置為 24dp,字體大小設置為 16sp。幾乎在所有手機上顯示都沒有問題,但是當 Nokia 安卓手機面世之后,突然 “清空” 按鈕被 cut 掉了一半,只顯示 “清”,原因就是 24sp 在 Nokia 手機上計算出的寬度不足以展示 2 個 16sp 大小的文字。
對於 TextView 還有一種情況要注意,我們要習慣使用一個極長字符串來測試在某些極端情況下 TextView 的顯示情況。因為需求文檔上給到的大多是一個比較常規的文本內容,但是我們從后端獲取的文本字符串有時是用戶自定義的,有可能是一個比較長的文本字符串。調試時期可以使用 tools:text 屬性來調試,tools 屬性只是在預覽界面有效,比如以下配置:

上圖中的 TextView 在 AS 的預覽界面會顯示“這是一段超長的調試文本內容”,但是當安裝到手機上時,顯示的是“文本內容”。
圖片ImageView
對於 ImageView 不建議統一使用 wrap_content,因為有時我們的圖片是從服務器上下載到本地顯示的,圖片的寬高並不一定是完全相等的,這樣會造成圖片的顯示大小不一致,這種情況我們一般是將 ImageView 的寬高設置為某一固定 dp 值。還有另外一種做法就是在 Java 代碼中動態設置 ImageView 的大小,一個比較常見的使用場景就是 RecyclerView Item 分屏顯示,比如需求是 RecyclerView 中每一個 item 大小為屏幕的 1/3,這種情況我們就可以考慮在代碼中動態設置 item view 的大小,如下所示:


————來自拉勾教育筆記
