今天要實現的效果如下,支持數據直接寫入或者在 XML 中佈局
一、自定義 ViewGroup
從上面的效果看,自定義有挺多種選擇,比如繼承 LinearLayout 或者 HorizontalScrollView … ,但其實直接繼承ViewGroup去動態測量更香;
首先,步驟也很簡單:
- 繼承 ViewGroup
- 重寫 onMeasure,計算子控件的大小從而確定父控件的大小
- 重寫 onLayout ,確定子控件的佈局
直接看第二部,由於是橫向,那麼如果控件是wrap_content ,則需要拿到子控件的大小,然後給父控件,如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int childCount = getChildCount();
int width = 0;
int height = 0;
/**
* 計算寬高,由於是橫向 width 應該是所有子控件的累加,不用管模式了
*/
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() == View.GONE){
continue;
}
measureChild(child, widthMeasureSpec, heightMeasureSpec);
MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
//拿到 子控件寬度
int cw = child.getMeasuredWidth() + params.leftMargin + params.rightMargin;
int ch = child.getMeasuredHeight() + params.topMargin + params.bottomMargin;
width += cw;
//拿到 子控件高度,拿到最大的那個高度
height = Math.max(height, ch);
}
if (MeasureSpec.EXACTLY == heightMode) {
height = heightSize;
}
setMeasuredDimension(width, height);
}
看到上面有同學會問了,你怎麼可以把控件的 LayoutParams 轉成 MarginLayoutParams 呢?不怕報錯嗎?
是的,會報錯,但是爲什麼可以強轉呢?其實跟下面4個方法有關(可以參考LinearLayout 的源碼):
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return new MarginLayoutParams(p);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected boolean checkLayoutParams(LayoutParams p) {
return p instanceof MarginLayoutParams;
}
}
接着在 onLayout 中,擺放子控件的位置:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
int left = 0;
int top = 0;
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
int cl = left + params.leftMargin;
int ct = top + params.topMargin;
int cr = cl + child.getMeasuredWidth() ;
int cb = ct + child.getMeasuredHeight();
//下個控件的起始位置
left += child.getMeasuredWidth() + params.leftMargin + params.rightMargin;
child.layout(cl, ct, cr, cb);
}
}
這樣,我們的測量和擺放就已經弄好了,接着就是我們在 xml 中添加一些數據:
<com.zhengsr.tablib.FlowLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#15000000"
>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="001"/>
....
</com.zhengsr.tablib.FlowLayout>
運行看一下,發現子控件按照我們的方式,擺放好了:
二、完善代碼
看上去已經實現了我們的效果,但是給FlowLayout加上padding呢?
<com.zhengsr.tablib.FlowLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:background="#15000000"
>
....
咦,有點不對;所以還得修改;
想一下,padding 會影響哪些部分呢?
FlowLayout 的寬度肯定不會受影響,但是高度是會的;然後onLayout 中,子控件的初始位置,應該要加上 padding,所以,修改後的代碼,應該是這樣:
重新運行一下;發現已經ok了: