一、ViewGroup基礎
ViewGroup是Android中所有布局的基類,它充當了容器的角色,可以容納其他View。比如LinearLayout、RelativeLayout、FrameLayout、GridLayout等都是ViewGroup的子類。
一個ViewGroup可以有多個子View,這些子View可以以不同的方式排列,ViewGroup需要根據它的布局方式來決定每個子View的位置和大小。ViewGroup的布局方式通過它所處的布局文件的xml來定義,或者通過代碼來設置。
二、自定義ViewGroup
1. 繼承ViewGroup並重寫onMeasure方法
public class CustomViewGroup extends ViewGroup{
public CustomViewGroup(Context context){
super(context);
}
public CustomViewGroup(Context context, AttributeSet attrs){
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 自定義測量代碼
// ...
// 設置最終測量寬高
setMeasuredDimension(widthSize, heightSize);
}
}
在自定義ViewGroup時,我們需要實現onMeasure()方法來進行布局的測量。onMeasure()方法的運行原理是:系統會先調用setMeasuredDimension()方法來確定ViewGroup的測量寬高,然後再調用每個子View的measure()方法來測量它們的寬高,最後根據子View的測量結果來確定它們的位置和大小。
在onMeasure()方法中,我們需要參考父容器所傳遞進來的 widthMeasureSpec 和 heightMeasureSpec 參數,來計算出我們ViewGroup的寬高,然後再通過 setMeasuredDimension()方法來設置它們。widthMeasureSpec 和 heightMeasureSpec 的具體含義可以參考Android官方文檔。
2. 重寫onLayout方法
public class CustomViewGroup extends ViewGroup{
public CustomViewGroup(Context context){
super(context);
}
public CustomViewGroup(Context context, AttributeSet attrs){
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 自定義測量代碼
// ...
// 設置最終測量寬高
setMeasuredDimension(widthSize, heightSize);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// 自定義布局代碼
// ...
}
}
ViewGroup的布局過程除了測量階段之外,還有一個布局階段。在測量階段,ViewGroup會根據子View自身的大小來確定它們的位置和大小,但在布局階段,ViewGroup則需要根據它的布局方式來決定子View的位置和大小。
我們需要實現onLayout()方法來進行布局的計算。onLayout()方法會在子View的測量階段之後執行,這時我們已經知道了每個子View的大小和位置。我們只需要按照布局方式將它們放置到合適的位置即可。
三、實現一個簡單的自定義ViewGroup
1. 布局方式
假設我們要實現一個自定義ViewGroup,它的布局方式為兩行兩列,每個子View大小相等。我們首先需要確定每個子View的位置和大小。我們將ViewGroup分成4份,然後將每個子View放置在相應的位置。
2. 代碼實現
public class CustomViewGroup extends ViewGroup {
private int mChildWidth, mChildHeight;
public CustomViewGroup(Context context) {
this(context, null);
}
public CustomViewGroup(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 計算每個子View的大小
int screenWidth = getResources().getDisplayMetrics().widthPixels;
mChildWidth = (int) (screenWidth * 0.4f);
mChildHeight = (int) (mChildWidth * 1.2f);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 計算ViewGroup的總寬度
int width = MeasureSpec.getSize(widthMeasureSpec);
int totalPadding = getPaddingLeft() + getPaddingRight();
int availableWidth = width - totalPadding;
int columnCount = 2;
int childWidth = (availableWidth - (columnCount - 1) * mChildWidth) / columnCount;
int childHeight = (int) (childWidth * 1.2f);
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY);
// 測量每個子View的大小
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
// 計算ViewGroup的總高度
int rowCount = childCount % 2 == 0 ? childCount / 2 : childCount / 2 + 1;
int totalHeight = rowCount * childHeight + (rowCount - 1) * mChildHeight + getPaddingTop() + getPaddingBottom();
setMeasuredDimension(width, totalHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
int width = r - l;
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int childTop = paddingTop;
// 遍歷每個子View,設置它們的位置
for (int i = 0; i < count; i++) {
View childView = getChildAt(i);
int childLeft = paddingLeft + (mChildWidth + width - paddingLeft - getPaddingRight() - 2 * mChildWidth) * (i % 2);
if (i % 2 == 0) {
childTop += i == 0 ? 0 : (mChildHeight + childView.getMeasuredHeight());
}
childView.layout(childLeft, childTop, childLeft + mChildWidth, childTop + childView.getMeasuredHeight());
}
}
}
3. 使用
我們可以在xml布局文件中使用我們的自定義ViewGroup,如下所示:
<com.example.CustomViewGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<Button
android:text="Button 1"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:text="Button 2"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:text="Button 3"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:text="Button 4"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.example.CustomViewGroup>
注意到我們把ViewGroup的padding設置成了16dp,並為每個子View設置了match_parent作為它們在ViewGroup中的寬度,這樣才能保證每個子View的大小一致。
四、結語
自定義ViewGroup是Android開發中很重要的一個方面。只要你理解了ViewGroup的測量和布局原理,就可以通過自定義ViewGroup達到各種複雜的布局效果。同時,ViewGroup也是自定義View中最常用、最基礎的一部分。學會了自定義ViewGroup,你也就能夠更好的理解自定義View的其他部分。
原創文章,作者:BFBOM,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/334895.html