本文來自 網易雲社區 。 問題描述 Android App中的頁面元素,都是由一個個Box(可以理解成一個個自定義View組件和Widget同級)組成,這些Box可以在不同的頁面、不同的模塊達到復用的效果。但是,現在遇到了一個對於開發復用棘手的問題, A頁面的組件間距和B頁面的組件間距可能不同。 A ...
本文來自 網易雲社區 。
問題描述
Android App中的頁面元素,都是由一個個Box(可以理解成一個個自定義View組件和Widget同級)組成,這些Box可以在不同的頁面、不同的模塊達到復用的效果。但是,現在遇到了一個對於開發復用棘手的問題,
- A頁面的組件間距和B頁面的組件間距可能不同。
- A頁面的Box1與Box1間距,和Box1與Box2的間距不一樣。
- Box和Box之間的分割線,有粗有細,有的有左邊距。
等等還有許多需要動態調整的地方。
然後做這些Box組件,就是為了復用它們,但現在又要對於每個Box的外邊距,如Padding值,進行修改,以適應不同頁面的要求。
方案
方案一
當然首先想到的是每個Box就和View一樣,都有自己的屬性,可以更改它的邊距等屬性,每個Box都有自己的視圖模型ViewModel,可以在ViewModel中添加配置項,如:
public class ViewModel {
int leftPadding;
int topPadding;
int rightPadding;
int bottomPadding;
public void setPadding(int left, int top, int right, int bottom
{...}
}
//在Box的update方法,根據配置的ViewModel,更新視圖
public void update() {
if(viewModel != null) {
setPadding(viewModel.leftPadding, viewModel.topPadding,
viewModel.rightPadding, viewModel.bottomPadding);
}
}
這種辦法,確實可行,但是會增加了開發的工作量,增加了代碼的繁瑣程度。畢竟Box的ViewModel應該儘量簡單,ViewModel的存在應該只是讓內部可以配置,而且主要配置的是Box要顯示的數據。
如果每個要復用的Box都要增加這些屬性,然後適配ViewModel的時候還要考慮每一個Box的四周邊距,有時候還不能統一對待。比如一個List存放著同一種類型的Box,但是恰好最後一個Box它需要底邊距不一樣,那豈不還要適配ViewModel的時候還要做序數判斷?
可想而知,開發代價是多高。
方案二
下麵講個實踐中覺得最好的方案: 每個頁面其實就是負責組裝這些Box,因此負責組裝的頁面應該知道Box與Box之間的邊距,分割線等外置屬性。因此這些外置邊距就交給RecyclerView.ItemDecoration
類。
首先我們需要知道RecyclerView.ItemDecoration
類能幹什麼?
假設綠色區域代表的是我們的內容,紅色區域代表我們自己繪製的裝飾,可以看到:
圖1:代表了getItemOffsets(),可以實現類似padding的效果。
圖2:代表了onDraw(),可以實現類似繪製背景的效果,內容在上面。
圖3:代表了onDrawOver(),可以繪製在內容的上面,覆蓋內容。
不過在交給它之前,還需要明確Box的邊界。
- 藍色框內為可復用的Box
- 紅色外框是RecycleView中的一個ChildView大小
然後就有瞭如下方案:
約定:要復用的Box內部不應該有距離上下左右的邊距,要保證最小邊界。
Box與Box之間的邊距交給RecyclerView.ItemDecoration
的getItemOffset()
方法處理。
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
DividerDesc dividerDesc = getDividerDesc(view, parent);
outRect.set(dividerDesc.leftPadding, dividerDesc.topPadding, dividerDesc.rightPadding, dividerDesc.bottomPadding);
}
我們可以通過getDividerDesc()方法,通過當前View及RecycleView里的Adapter里的ItemType。得知當前View需要在四周有怎麼樣的屬性及分割線。
DividerDesc
類可以是這樣:
private static class DividerDesc {
final boolean needLeftMargin; //水平分割線左邊距
final int leftPadding; //左邊距
final int topPadding; //上邊距
final int rightPadding; //右邊距
final int bottomPadding; //底邊距
final int dividerHeight; //底部分割線高度
DividerDesc(boolean needLeftMargin, int leftPadding, int topPadding, int rightPadding, int bottomPadding, int dividerHeight) {
this.needLeftMargin = needLeftMargin;
this.leftPadding = leftPadding;
this.topPadding = topPadding;
this.rightPadding = rightPadding;
this.bottomPadding = bottomPadding;
this.dividerHeight = dividerHeight;
}
DividerDesc() {
this(false, 0, 0, 0, 0, 0);
}
}
在RecyclerView.ItemDecoration
的drawOver()
方法中,畫出需要分割線的地方。
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
int left,right,top,bottom;
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount - 1; i++) {
left = parent.getPaddingLeft();
right = parent.getWidth() - parent.getPaddingRight();
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
DividerDesc dividerDesc = getDividerDesc(child, parent);
//帶左邊距
if (dividerDesc.needLeftMargin) {
left += sThinMarginHorizontal;
}
bottom = child.getBottom() + dividerDesc.bottomPadding;
top = bottom - dividerDesc.dividerHeight;
if (dividerDesc.dividerHeight == sThinHeight) {
//畫細線
mDividerThin.setBounds(left, top, right, bottom);
mDividerThin.draw(c);
} else if (dividerDesc.dividerHeight == sThickHeight){
//畫粗線
mDividerThick.setBounds(left, top, right, bottom);
mDividerThick.draw(c);
}
}
Demo效果如下:
未加邊距:
增加邊距:
如果你也遇到了為Box復用而沒法處理繁瑣的視覺調整問題,或者有更好的方案,歡迎一塊討論 :)
參考文章
本文已由作者陳柏寧授權網易雲社區發佈,原文鏈接:Box(視圖組件)如何在多個頁面不同視覺規範下的復用