最近做完一个自定义ViewGroup 实现流式布局的效果,效果如下
简单来说就是继承ViewGroup,onMeasure onLayout 各种测试、布局,然后在xml引入该自定义View使用即可
重点来了,然后自己实现一个自定义LinearLayout的时候,还是按部就班
onMeasure
onLayout
....省略计算过程
然后调用View.Layout(l,t,r,b);
发现设置的距离完全不起作用,效果如下
位置随意设置,完全没用!!
一番折腾,想起来,LinearLayout属于ViewGroup,而ViewGroup中的onLayout方法是个抽象方法,需要它的子类自己去实现,因为像LinearLayout、RelativeLayout等,他们的布局方式都是不一样,所以,一头扎进LinearLayout的源码中查看是怎么实现的布局
这个很容易,分为垂直布局和水平两种,以水平为例:
for (int i = 0; i < count; i++) {
final int childIndex = start + dir * i;
//1.获取每个view
final View child = getVirtualChildAt(childIndex);
if (child == null) {
2.如果child为空,measureNullChild返回的0 childLeft不变
childLeft += measureNullChild(childIndex);
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
int childBaseline = -1;
//3.拿到每个view的params参数
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
if (baselineAligned && lp.height != LayoutParams.MATCH_PARENT) {
childBaseline = child.getBaseline();
}
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
case Gravity.TOP:
childTop = paddingTop + lp.topMargin;
if (childBaseline != -1) {
childTop += maxAscent[INDEX_TOP] - childBaseline;
}
break;
case Gravity.CENTER_VERTICAL:
// Removed support for baseline alignment when layout_gravity or
// gravity == center_vertical. See bug #1038483.
// Keep the code around if we need to re-enable this feature
// if (childBaseline != -1) {
// // Align baselines vertically only if the child is smaller than us
// if (childSpace - childHeight > 0) {
// childTop = paddingTop + (childSpace / 2) - childBaseline;
// } else {
// childTop = paddingTop + (childSpace - childHeight) / 2;
// }
// } else {
childTop = paddingTop + ((childSpace - childHeight) / 2)
+ lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = childBottom - childHeight - lp.bottomMargin;
if (childBaseline != -1) {
int descent = child.getMeasuredHeight() - childBaseline;
childTop -= (maxDescent[INDEX_BOTTOM] - descent);
}
break;
default:
childTop = paddingTop;
break;
}
//4.是否有设置分割,如果有,距离还需要考虑设置的dividerWidth
if (hasDividerBeforeChildAt(childIndex)) {
childLeft += mDividerWidth;
}
//5.关键!! childLeft的计算方式
childLeft += lp.leftMargin;
//6.调用该方法设置
setChildFrame(child, childLeft + getLocationOffset(child), childTop,
childWidth, childHeight);
childLeft += childWidth + lp.rightMargin +
getNextLocationOffset(child);
i += getChildrenSkipCount(child, childIndex);
}
}
看到第五、六步发现,LinearLayout是以左侧为基准,处理子View的位置排布的,其中,涉及到了一个很重要的属性margin,它是通过循环,控制childLeft的值,然后调用setChildFrame方法,在该方法中,最终调用的view.Layout方法
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
layout中left为chilldLeft + 偏移量, right 就刚好再加上view本身的宽度
所以在外部的onLayout方法中,无论你怎么设置四个参数,压根就影响不到有用的的布局参数,所以,需要给view设置它们的params就可以了
根据源码,如果需要控制上间距和下间距,同样控制topMargin或者bottomMargin即可,在源码中的switch语句可以看到
修改代码
给定view的leftMargin值,再次运行
OK,达到了预期的效果
结论:不同的ViewGroup,都单独实现了自己的onLayout方法,这也是为什么继承ViewGroup,需要强制重写onLayout的原因
伪代码即:
源码都已经安排的明明白白,收工!