这篇文章我们开始向ViewGroup进军,打造一个简单的LinearLayout,当然该LinearLayout还不能用于项目中,只是为了学习使用。
首先new一个class, 让它继承自ViewGroup, 默认会让你重写onLayout方法,因为该方法在ViewGroup中是abstract的:
@Override
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
但是只重写onLayout还是不够的, 还要重写onMeasured方法用来测量子view和保存自己最终的大小。
哦,对了,还记得layout_width、layout_height吗? 这些其实不是view的属性,而是ViewGroup的, 即view告诉ViewGroup我要的宽度和高度,我们的LinearLayout也需要一个LayoutParams:
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
重写ViewGroup的generateLayoutParams方法,直接返回一个简单的MarginLayoutParams,因为我们需要view的margin。
好吧, 看看我们在onMeasured方法中做了什么,onMeasure方法主要的工作就是测量子view的大小,并确定自己的大小:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int childCount = getChildCount();
// measureChildren(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width = 0;
int height = 0;
for(int i=0;i<childCount;i++) {
View view = getChildAt(i);
if(view.getVisibility() == View.GONE) {
continue;
}
measureChild(view, widthMeasureSpec, heightMeasureSpec);
MarginLayoutParams p = (MarginLayoutParams) view.getLayoutParams();
width = Math.max(width, view.getMeasuredWidth() + p.leftMargin + p.rightMargin);
height += view.getMeasuredHeight() + p.topMargin + p.bottomMargin;
}
// 如果mode是EXACTLY, 则设置为父布局传过来的值
// 如果是AT_MOST, 则设置为自己测量的结果
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize
: width, heightMode == MeasureSpec.EXACTLY ? heightSize
: height);
}
6~9行,使用MeasureSpec的两个静态方法,分别获取了width的mode、size和height的mode、size,mode是干什么的?如果mode==MeasureSpec.EXACTLY的话,我们就确定ViewGroup的大小为父布局给我们建议的大小,如果是 MeasureSpec.AT_MOST的话,就需要我们自己测量了。
14~24行,遍历所有的子view,首先调用ViewGroup的一个方法measueChild来测量一下该view,要么view.getMeasuredXXX()获取到的肯定是0。然后获取该view的宽度和高度,让ViewGroup的宽度等于当前ViewGroup宽度和该view宽度的最大值,让ViewGroup的高度累加上当前View的高度。
最后调用setMeasuredDimension来设置测量结果,可以看到这里有一个条件,表示如果mode是MeasureSpec.EXACTLY的话,我们不去理会自己的测量结果,直接将父布局给出的建议大小保存起来就ok。
onLayout需要做的是确定子view的位置:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int childCount = getChildCount();
int height = 0;
for(int i=0;i<childCount;i++) {
View view = getChildAt(i);
if(view.getVisibility() == View.GONE) {
continue;
}
MarginLayoutParams p = (MarginLayoutParams) view.getLayoutParams();
MarginLayoutParams p = (MarginLayoutParams) view.getLayoutParams();
view.layout(p.leftMargin, height + p.topMargin,view.getMeasuredWidth() + p.rightMargin,height + view.getMeasuredHeight() + p.topMargin + p.bottomMargin);
height += view.getMeasuredHeight() + p.topMargin + p.bottomMargin;
}
}
在布局文件中使用MyLinearLayout:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<org.loader.mylinearlayout.MyLinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/holo_green_light" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text2" />
<TextView
android:layout_marginTop="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text3" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text4" />
<TextView
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text" />
</org.loader.mylinearlayout.MyLinearLayout>
</RelativeLayout>
达到我们的效果了。当然这还是最简单的ViewGroup,google官方的api中也有一个demo,很适合我们学习,感兴趣的可以看看。