本文是一種奇怪又常見的佈局需求實現方案的記錄。
具體需求長這樣子:
- 顯示使用者名稱和使用者 ID
- 整體寬度不能固定,要跟隨內容變化
- 空間不夠的話優先顯示ID,擷取使用者名稱過長部分顯示為“…”
抽象起來就是多個元素橫向排列,在空間不足的小屏手機上,保證顯示右邊的元素,擠壓左邊的。
怎麼實現呢?
好簡單啊,一開始我是這樣想,腦子裡已經瞬間想好兩個方法 —— RTL 屬性, Weight 屬性。然而真正實現的時候發現它們都有些小缺陷,最後兩個都沒采用,而是重寫了 measure。過程頗費周折。
把這3種方法和對應的缺點記錄在了本文,作為備忘。如果能順便幫到你,那再好不過了。
RTL屬性
簡單來說,從 Android 4.2開始,Android SDK 支援一種從右到左(RTL,Right-to-Left)UI 佈局的方式,儘管這種佈局方式經常被使用在諸如阿拉伯語、希伯來語等環境中,中國使用者很少使用。不過在某些特殊用途中還是很方便的…才怪!
很多機子不能很好地支援 RTL,無法得到一致的外觀。啟用 RTL 也有點麻煩的,而且在寫很多佈局屬性如padding時都要注意做一些轉換計算。關於RTL的詳細介紹和使用點這裡。
所以,RTL,撲街了。
Weight
LinearLayout 中的控制元件可以新增 layout_weight 屬性。LinearLayout 在分配空間時會先分配沒有設定 Weight 的元素,然後對當前剩餘空間按Weight比例分配給設定了 Weight 的元素。
這個屬性可以很好的應對那些內容會動態變化的佈局結構。屬於 Android 佈局的基礎,再具體的不在這裡敘述。
至此一切都很順利,問題出在把她放在 RecyclerView 中的時候,假如不固定 LinearLayout 的寬度(wrap_content),因為一些 View 重用的機制,notify adapter 時寬度偶爾會亂套。一些長的內容會誤設到之前的短內容寬度的 LinearLayout 中。
如果是非列表佈局,或者是可以確定 LinearLayout 寬度的情況下,Weight 屬性其實非常好用。
但是很遺憾,這次 Weight,也撲街。
重寫 measure
沒辦法,只能自己做點處理了。
仔細想想,LinearLayout 本來就是從左到右佈局,空間不夠時擠壓右邊,我們只要反過來就可以了。
那就先繼承 LinearLayout:
public class RearFirstLinearlayout extends LinearLayout {}複製程式碼
呼叫父類的 onMeasure 方法以正常使用各種好用的 layout 屬性,如 layout_gravity:
java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}複製程式碼
然後在 Measure 的時候優先分配右邊元素的空間:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 非橫排和不開啟RearFirst,則用系統預設measure
if (!isHorizon() || !isRearFirst) {
return;
}
int mWidth = MeasureSpec.getSize(widthMeasureSpec);//可用寬度
int mHeight = getMeasuredHeight();
int mCount = getChildCount();//子view數量
//計算預計總寬
int preComputeWidth = 0;
//臨時記錄位置
mLeft = 0;
mRight = 0;
mTop = 0;
mBottom = 0;
rest = mWidth;
for (int i = mCount - 1; i >= 0; i--) {//從後往前算
Log.i("measure-i:", "" + i);
final View child = getChildAt(i);
int spec = MeasureSpec.makeMeasureSpec(rest, MeasureSpec.AT_MOST);
child.measure(spec, MeasureSpec.UNSPECIFIED);
int childw = child.getMeasuredWidth();
int childh = child.getMeasuredHeight();
preComputeWidth += childw;
// 計算rest
mRight = getPositionRight(i, mCount, mWidth);
mLeft = mRight - childw;
mBottom = mTop + childh;
rest = mLeft;
}
//儲存最終的測量結果
if (preComputeWidth<=mWidth){
setMeasuredDimension(preComputeWidth, mHeight);
}else {
setMeasuredDimension(mWidth, mHeight);
}
}複製程式碼
歐了。
2017.04.05 更新,補充FlexboxLayout方案。
Flexbox 是屬於web前端領域CSS的一種佈局方案,FlexboxLayout是Google出品的一款類似 Flexbox 的佈局,主要解決各種UI比例劃分的問題。可以看到裡面簡單的一個layout_flexShrink屬性就可以解決問題。
layout_flexShrink 屬性定義了專案的縮小比例,預設為1,即如果空間不足,該專案將縮小。
如果所有專案的 layout_flexShrink 屬性都為1,當空間不足時,都將等比例縮小。如果一個專案的flex-shrink屬性為0,其他專案都為1,則空間不足時,前者不縮小。
FlexboxLayout 很強大,這不過她的一個小功能。只是不想因為一個小問題就引個庫進來,所以沒采用。而且 FlexboxLayout 版本還沒到1.0.0。
2017.04.05 2次更新,評論裡提到的Android原始碼裡面實現類似功能的EllipsizeLayout,也放上來吧。
思路類似,不過 EllipsizeLayout 實現得比較細緻,還考慮了gone了的view等處理。