Android中自定义LinearLayout中layout方法不起作用的原因

最近做完一个自定义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的原因

伪代码即:

源码都已经安排的明明白白,收工!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值