android 自定义控件中的测量和布局

上一篇:android onMeasure 实现

上一篇中其实对测量讲解的比较清楚了,对布局没有说。不过对于 viewGroup而言,测量完成了,也就意味着着,知道怎么布局了。

其实知道了怎么测量和布局就可以实现很多沙雕的控件了。比如流布局。当然了,如果想提供很方便的调用方式,还需要搞一些便捷的自定义属性才行。

这里展示一下,通过测量和布局实现的沙雕布局。

在这里插入图片描述

在这里插入图片描述

通过效果图可以看出来,这就是流布局了。而且带两种效果:一种就是普通的流布局,一行一行的排列;然后第二种是竖直排列的效果,是一列一列的排列。

当然了,这两种的代码差别其实不大,主要是 在测量和布局的时候,要计算好每个 item 应该占据的尺寸和位置。

大致说一下原理。

比如这种横向排列的流布局。在测量的时候,首先肯定也是测量每个 child的宽高。然后需要判断,当前这个 child是挨着上一个的后面继续排,还是换行。然后对于布局,也是一样的,如果不换行,那么当前这个 child一定是排在上一个的后面,就要计算好当前的childl,t,r,p(上下左右四个顶点)对应的位置距离。如果换行,也是要计算这4个值。只是计算的逻辑有区别。

核心逻辑,关于计算和判断是否需要换行的代码就是下面这几行:

	if (lineWidth + itemW > ws - getPaddingLeft() - getPaddingRight()) {
	    // 换行 ,先记录换行之前的数据
	     width = Math.max(width, lineWidth); // 记录的是上一行的数据
	     height += lineHeight; // 记录的是上一行的数据
	
	     // 换行了
	     lineHeight = itemH;
	     lineWidth = itemW;
	 } else {
	     // 不换行
	     lineHeight = Math.max(lineHeight, itemH);
	     lineWidth += itemW;
	 }
	
	 if (i == count - 1) {
	     // 最后一个 view , 记录这一行的数据
	     width = Math.max(width, lineWidth); // 记录的是这一行的数据
	     height += lineHeight; // 记录这一行的数据
	
	 }
	 // width/height 是 将要设置给自己(当前自定义的这个ViewGroup)的宽度/高度

看起来代码很少,而且也容易理解。但是,我要说的是,就是这里的逻辑搞得我头大,我之前想了很久,代码写的比这里复杂多了,但是没有实现想要的效果。(就是可以继续挨着就挨着,否则换行的效果)

实在没有想到要怎么实现这个逻辑,然后查了一些资料,终于写成上面这样的代码了。(可以说上面的这几行代码并非原创。)

要特别注意当前计算出来的值,是当前行的数据,还是上一行的数据。一定要计算出当前行的。

然后要注意对 child支持padding / margin的设置支持。(也是在测量和布局的时候做的逻辑)

彩蛋来了完整代码,star me


ViewGroup 设置 divider , 很多系统的容器的控件都提供了设置分割线的属性或者方法。比如LinearLayout / ListView等。辣么,这都是怎么实现的呢?

其实分割线并不是一条线,当然理论上可以是一条线。不过一般系统控件都是通过 Drawable去实现的。

通过查看系统控件 LinearLayout的源码可以大致知道其实现方式:

  1. 首先需要让 ViewGroup 能够触发 onDraw方法。[该方法在ViewGroup中默认不触发]

        setWillNotDraw(!isShowingDividers());
        requestLayout();
  1. 然后在测量的时候,要把 divider 的宽高也计算进去。
  2. 在布局的时候,也是要让出 divider的位置。
  3. 在 绘制的时候,在对应的位置上面进行绘制即可。

实现一个类似竖直排列的线性布局 的 分割线

    @Override
    protected void onDraw(Canvas canvas) {
        int t = getPaddingTop();
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            if (child.getVisibility() == GONE) {
                continue;
            }
            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            int l = lp.leftMargin + getPaddingLeft();
            t = t + lp.topMargin;
            int r = l + child.getMeasuredWidth();
            int b = t + child.getMeasuredHeight();
//            child.layout(l, t, r, b);
            t += child.getMeasuredHeight() + lp.bottomMargin + dividerHeight;
            if (i < getChildCount() - 1) {
                mDivider.setBounds(getPaddingLeft(), b + lp.bottomMargin, getRight()-getPaddingRight(), b + lp.bottomMargin + dividerHeight);
                mDivider.draw(canvas);
            }

        }
    }

其实绘制的关键代码只有两行:

//  LinearLayout.java 中的源码
    void drawHorizontalDivider(Canvas canvas, int top) {
        mDivider.setBounds(getPaddingLeft() + mDividerPadding, top,
                getWidth() - getPaddingRight() - mDividerPadding, top + mDividerHeight);
        mDivider.draw(canvas);
    }

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值