移動開發 如何自定義ViewGroup

2021-06-25 11:00:05 字數 4126 閱讀 4079

本文翻譯自《50 android hacks》

依照慣例,先從乙個例子說起。

很簡單,3張撲克牌疊在一起顯示。這個布局效果該如何實現呢?有的同學該說了,這很簡單啊,用relativelayout或framelayout,然後為每乙個撲克牌設定margin就能實現了。

ok,那就看一下通過這種方式是如何實現的。**如下:

效果圖

沒錯,通過這種方式是可以實現的。但是,不覺得這種方式有點low嗎?!讓我們用高階一點的方式去實現它,提公升一下自己的逼格!

定製viewgroup之前,我們需要先理解幾個概念。

android繪製檢視的方式

這裡我不會涉及太多的細節,但是需要理解android開發文件中的一段話:

「繪製布局由兩個遍歷過程組成:測量過程和布局過程。測量過程由measure(int, int)方法完成,該方法從上到下遍歷檢視樹。在遞迴遍歷過程中,每個檢視都會向下層傳遞尺寸和規格。當measure方法遍歷結束,每個檢視都儲存了各自的尺寸資訊。第二個過程由layout(int,int,int,int)方法完成,該方法也是由上而下遍歷檢視樹,在遍歷過程中,每個父檢視通過測量過程的結果定位所有子檢視的位置資訊。」

簡而言之,第一步是測量viewgroup的寬度和高度,在onmeasure()方法中完成,viewgroup遍歷所有子檢視計算出它的大小。第二步是根據第一步獲取的尺寸去布局所有子檢視,在onlayout()中完成。

建立cascadelayout

終於到了定製viewgroup的階段了。假設我們已經定製了乙個cascadelayout的容器,我們會這樣使用它。

首先,定義屬性。在values資料夾下面建立attrs.xml,**如下:

同時,為了嚴謹一些,定義一些預設的垂直距離和水平距離,以防在布局中沒有提供這些屬性。

在dimens.xml中新增如下**:

10dp

10dp

準備工作已經做好了,接下來看一下cascadelayout的原始碼,略微有點長,後面幫助大家分析一下。

public class cascadelayout extends viewgroup  finally 

} @override

protected void onmeasure(int widthmeasurespec, int heightmeasurespec)

width += child.getmeasuredwidth();

height += verticalspacing;

}width += getpaddingright();

height += getchildat(getchildcount() - 1).getmeasuredheight()

+ getpaddingbottom();

setmeasureddimension(resolvesize(width, widthmeasurespec),

resolvesize(height, heightmeasurespec));

} @override

protected void onlayout(boolean changed, int l, int t, int r, int b)

} @override

protected boolean checklayoutparams(viewgroup.layoutparams p)

@override

protected layoutparams generatedefaultlayoutparams()

@override

public layoutparams generatelayoutparams(attributeset attrs)

@override

protected layoutparams generatelayoutparams(viewgroup.layoutparams p)

public static class layoutparams extends viewgroup.layoutparams

public layoutparams(int w, int h)

}}

首先,分析建構函式。

public cascadelayout(context context, attributeset attrs)  finally 

}

如果在布局中使用casecadelayout,系統就會呼叫這個建構函式,這個大家都應該知道的吧。這裡不解釋why,有興趣的可以去看原始碼,重點看系統是如何解析xml布局的。

建構函式很簡單,就是通過布局檔案中的屬性,獲取水平距離和垂直距離。

然後,分析自定義layoutparams。

public static class layoutparams extends viewgroup.layoutparams 

public layoutparams(int w, int h)

}

除此之外,還需要重寫一些方法,checklayoutparams()、generatedefaultlayoutparams()等,這個方法在不同viewgroup之間往往是相同的。

接下來,分析onmeasure()方法。

@override

protected void onmeasure(int widthmeasurespec, int heightmeasurespec)

width += child.getmeasuredwidth();

height += verticalspacing;

}width += getpaddingright();

height += getchildat(getchildcount() - 1).getmeasuredheight()

+ getpaddingbottom();

// 使用計算所得的寬和高設定整個布局的測量尺寸

setmeasureddimension(resolvesize(width, widthmeasurespec),

resolvesize(height, heightmeasurespec));

}

最後,分析onlayout()方法。

@override

protected void onlayout(boolean changed, int l, int t, int r, int b)

}

邏輯很簡單,用onmeasure()方法計算出的值為引數迴圈呼叫子view的layout()方法。

為子檢視新增自定義屬性

作為示例,下面將新增子檢視重寫垂直間距的方法。

第一步是向attrs.xml中新增乙個新的屬性。

這裡的屬性名是layout_vertical_spacing,因為該屬性名字首是layout_,同時,又不是view固有的屬性,所以該屬性會被新增到layoutparams的屬性表中。在cascadelayout類的建構函式中讀取這個新屬性。

public static class layoutparams extends viewgroup.layoutparams  finally 

}public layoutparams(int w, int h)

}

那怎麼使用這個屬性呢?so easy!

參考資料

教你搞定Android自定義ViewGroup

我們知道viewgroup就是view的容器類,我們經常用的linearlayout,relativelayout等都是viewgroup的子類,因為viewgroup有很多子view,所以它的整個繪製過程相對於view會複雜一點,但是還是三個步驟measure,layout,draw,我們一次說明...

教你搞定Android自定義ViewGroup

我們知道viewgroup就是view的容器類,我們經常用的linearlayout,relativelayout等都是viewgroup的子類,因為viewgroup有很多子view,所以它的整個繪製過程相對於view會複雜一點,但是還是三個步驟measure,layout,draw,我們一次說明...

自定義 如何自定義協議

何為自定義協議,其實是相對標準協議來說的,這裡主要針對的是應用層協議 常見的標準的應用層協議如http ftp smtp等,如果我們在網路通訊的過程中不去使用這些標準協議,那就需要自定義協議,比如我們常用的rpc框架 dubbo,thrift 分布式快取 redis,memcached 等都是自定義...