在Android中,控制項可以分為ViewGroup控制項與View控制項。自定義View控制項,我之前的文章已經說過。這次我們主要說一下自定義ViewGroup控制項。ViewGroup是作為父控制項可以包含多個View控制項,並管理其中包含的View控制項。 一般自定義ViewGroup的流程如下: 我們一般不 ...
在Android中,控制項可以分為ViewGroup控制項與View控制項。自定義View控制項,我之前的文章已經說過。這次我們主要說一下自定義ViewGroup控制項。ViewGroup是作為父控制項可以包含多個View控制項,並管理其中包含的View控制項。
一般自定義ViewGroup的流程如下:
onMeasure()
onLayout()
我們一般不需要像自定義View一樣重寫onDraw(),這裡需要多寫一個onLayout來擺放子View的位置。除了onLayout方法之外,我們還需要確定LayoutParams,這個是子View告訴父佈局的一些參數信息,比如MarginLayoutParams,則表明該ViewGroup支持margin,當然這個也可以沒有。
下麵我們通過一個例子來說明簡單的自定義ViewGroup
一個簡單的例子
這個例子,我們將寫一個ViewGroup,該ViewGroup中4個button排成一列。這個例子主要說明onMeasure和onLayout的寫法。
首先我們新建一個MyViewGroup繼承ViewGroup,然後重寫onMeasure方法。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
measureChildren(widthMeasureSpec,heightMeasureSpec);
}
這個重寫非常的簡單,調用父類的測量方法,然後測量所有的子控制項的,只要子控制項不是wrap_content都會測量精準。這裡為了簡單,沒有去考慮wrap_content的情況,後面我們完善的時候會說道。
然後重寫onLayout()方法
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int height = 0;
int count = getChildCount();
View child;
Log.e("ri", count + "");
for(int i = 0 ;i < count;i++) {
child = getChildAt(i);
child.layout(0, height, child.getMeasuredWidth(),height + child.getMeasuredHeight());
height += child.getMeasuredHeight();
}
}
這裡的代碼很好理解,因為我們要實現4個button一列顯示,然後每個子View的寬度是一樣的,並且每個子View的left和right是一樣的。所以每個子View只有top和bottom不一樣。我們首先定義個高度height初始為0,然後得到所有子View的個數,依次設置每個子View的top和bottom。top就是定義的height,bottom則為height加上子View的高度。設置完後height累加。
xml中佈局如下:
<com.example.byhieg.viewpratice.MyViewGroup android:layout_height="match_parent"
android:layout_width="match_parent">
<Button
android:text="1"
android:layout_width="50dp"
android:layout_height="80dp" />
<Button
android:text="2"
android:layout_width="50dp"
android:layout_height="80dp" />
<Button
android:text="3"
android:layout_width="50dp"
android:layout_height="80dp" />
<Button
android:text="4"
android:layout_width="50dp"
android:layout_height="80dp" />
</com.example.byhieg.viewpratice.MyViewGroup>
效果如下:
下麵,我們繼續完善這個控制項,讓他適應wrap_content。這裡,我們主要重寫onMeasure()
@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);
measureChildren(widthMeasureSpec,heightMeasureSpec);
//開始處理wrap_content,如果一個子元素都沒有,就設置為0
if (getChildCount() == 0) {
setMeasuredDimension(0,0);
} else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
//ViewGroup,寬,高都是wrap_content,根據我們的需求,寬度是子控制項的寬度,高度則是所有子控制項的總和
View childOne = getChildAt(0);
int childWidth = childOne.getMeasuredWidth();
int childHeight = childOne.getMeasuredHeight();
setMeasuredDimension(childWidth, childHeight * getChildCount());
} else if (widthMode == MeasureSpec.AT_MOST) {
//ViewGroup的寬度為wrap_content,則高度不需要管,寬度還是自控制項的寬度
View childOne = getChildAt(0);
int childWidth = childOne.getMeasuredWidth();
setMeasuredDimension(childWidth,heightSize);
} else if (heightMode == MeasureSpec.AT_MOST) {
//ViewGroup的高度為wrap_content,則寬度不需要管,高度為子View的高度和
View childOne = getChildAt(0);
int childHeight = childOne.getMeasuredHeight();
setMeasuredDimension(widthSize, childHeight * getChildCount());
}
}
主要通過註釋,就可以很明白wrap_content的情況下,如何計算viewGroup的高度和寬度。在viewGroup的onMeasure,我們是不需要在這裡面考慮每一個View的寬高,這個通過measureChildren來通知每一個子View自己測量的。我們只需要考慮viewGroup的寬高在自適應的情況下,該是多大。
LayoutParams
在上面這個簡單的ViewGroup中,我們是沒有設置margin的,也就是說,即使我們在子View中設置了margin也是沒有效的。我們需要修改我們的自定義ViewGroup來適應margin的情況。這裡我們為了簡化情況,只設定第一個button有一個android:layout_margin="30dp"
的屬性。
這裡,我們修改onMeasure方法,讓viewGroup的寬度變為原來的寬度加上margin的寬度,高度也是原來的高度加上margin的高度。代碼如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
MarginLayoutParams params = null;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
measureChildren(widthMeasureSpec,heightMeasureSpec);
//開始處理wrap_content,如果一個子元素都沒有,就設置為0
if (getChildCount() == 0) {
setMeasuredDimension(0,0);
} else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
//ViewGroup,寬,高都是wrap_content,根據我們的需求,寬度是子控制項的寬度,高度則是所有子控制項的總和
View childOne = getChildAt(0);
params = (MarginLayoutParams) childOne.getLayoutParams();
int childWidth = childOne.getMeasuredWidth();
int childHeight = childOne.getMeasuredHeight();
setMeasuredDimension(childWidth + params.leftMargin + params.rightMargin,
childHeight * getChildCount() + params.topMargin + params.bottomMargin);
} else if (widthMode == MeasureSpec.AT_MOST) {
//ViewGroup的寬度為wrap_content,則高度不需要管,寬度還是自控制項的寬度
View childOne = getChildAt(0);
params = (MarginLayoutParams) childOne.getLayoutParams();
int childWidth = childOne.getMeasuredWidth();
setMeasuredDimension(childWidth + params.leftMargin + params.rightMargin,heightSize);
} else if (heightMode == MeasureSpec.AT_MOST) {
//ViewGroup的高度為wrap_content,則寬度不需要管,高度為子View的高度和
View childOne = getChildAt(0);
params = (MarginLayoutParams) childOne.getLayoutParams();
int childHeight = childOne.getMeasuredHeight();
setMeasuredDimension(widthSize, childHeight * getChildCount() + params.topMargin + params.bottomMargin);
}
}
這裡,註意這個語句params = (MarginLayoutParams) childOne.getLayoutParams();
如果不重寫layoutParams相關的代碼,這樣直接轉換會出現問題。所以,我們需要重寫如下代碼:讓他返回MarginLayoutParams類型的對象
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(),attrs);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
}
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return new MarginLayoutParams(p);
}
同樣,我們已經測量得到了viewGroup的寬和高,接下來,需要對添加了margin的view,重新擺放。主要的擺放規則,左邊的坐標為Leftmargin,第一個view的上面的坐標為topMargin,同時,第二個view的上面的坐標要加上bottomMargin。這個只是一個簡單的例子來說明放入margin之後要怎麼考慮,一般不會這麼具體到只計算第一個view的Margin。代碼看看就好
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int height = 0;
int count = getChildCount();
View child;
Log.e("ri", count + "");
child = getChildAt(0);
MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
int c1 = params.leftMargin;
int c2 = params.topMargin;
int c3 = c1 + child.getMeasuredWidth();
int c4 = c2 + child.getMeasuredHeight();
child.layout(c1,c2, c3,c4);
height = c4 + params.bottomMargin;
for(int i = 1 ;i < count;i++) {
child = getChildAt(i);
child.layout(c1, height, c3, height + child.getMeasuredHeight());
height += child.getMeasuredHeight();
}
}
總結
這是自定義ViewGroup的第一篇文章,自定義ViewGroup是比自定義View知識點更多,而且應用也更廣泛。這篇只是簡單的介紹了自定義ViewGroup中需要覆寫的方法,但是一般的viewGroup不會是這樣的。上面的東西,用一個LinearLayout佈局實現,然後include該佈局更方便。一般viewGroup都需要我們來實現view事件的分發以及滑動的處理,接下來的文章,講介紹滑動的多種方式。