Android自定义View之Layout内部布局自定义

目录

每日一句

作者简介 

概述 

1.定制Layout内部布局的方式步骤

1.1重写onMeasure()方法来计算内部布局 

        1.1.1 调用每个子View的measure(),让子View自我测量 

         1.1.2 根据子View给出的尺寸,得出子View 的位置,并保存它们的位置和尺寸

        1.1.3 根据子View 的位置和尺寸计算出自己的尺寸,并用setMeasuredDimension()保存

 1.2 重写onLayout()来摆放子View

        1.2.1 自我布局


每日一句

人生需要准备的,不是昂贵的茶,而是喝茶的心情

作者简介 

🏡个人主页:XiaoChen_Android

📚学习专栏:Android

🕒发布日期:2022/9/7

概述 

有的时候Android提供的五种布局不能满足我们的需求,于是我们必须根据项目需求自定义布局Layout,此篇主要讲通过重写onMeasure()方法和onLayout()方法来定制layout布局

其实内容还是很简单的,主要是一些概念和原理上的东西,实操方面还是很简单的

1.定制Layout内部布局的方式步骤

  1.  重写onMeasure()方法来计算内部布局
  2. 重写onLayout()方法来摆放子View

1.1重写onMeasure()方法来计算内部布局 

        1.1.1 调用每个子View的measure(),让子View自我测量 

        关键:1.关键:宽度和高度的MeasureSpec的计算

                   2.结合开发者的要求(layout_xxx)和自己的可用空间(自己的尺寸上限-已用尺寸

​
public class SomeLayout extends ViewGroup {

	@0verride
	protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
		for (int i = 0; i < getChildCount();i++){
			View childView = getChildAt(i);
			childView. onMeasure(childWidthSpec,childHeightSpec); //这两个参数是父View对子View的限制 而这两个参数并不是现成的,而是需要自己计算的
		}
	}
}

​

 onMeasure(childWidthSpec,childHeightSpec):高度宽度的尺寸限制计算规则:是把开发者的要求,也就是子View在xml文件里面的那些layout_XXX开头的参数,把他们和ViewGroup或者说Layout的剩余的可用空间,也就是还剩多少宽度和高度给子View去用,这二者结合起来计算得到的。 这两个要求中开发者的优先级要高于可用空间

对于每一个子View,计算他们的MeasureSpec尺寸限制的时候,依次查看他们的layout_widthlayout_height这两个属性,主要根据这两个。具体的要根据Layout来定 例如:RelativeLayout它的layout_alignParentTop和layout_toRightOf等这些定位置的参数,也有可能影响对子View的尺寸限制的计算;不过这些除了layout_width和 layout_height之外的属性,都取决于开发者,开发者不增加这些属性那就只要看着两个  

 对于每一个子View,查看他们的layout_width和layout_height这两个属性,分别用它们结合自己当前的的可用宽度和可用高度,来计算出子View的限制;查看方法:xml代码里的layout_width和layout_height在Java代码里会转换成View的两个属性,在父View里调用子View的getLayoutParams()方法,可以获得一个LayoutParams对象

public class SomeLayout extends ViewGroup {


	@0verride
	protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
		for (int i = 0; i < getChildCount();i++){
			View childView = getChildAt(i);
			LayoutParams lp = childView.getLayoutParams();  //包含了xml文件里layout_width和layout_height两个参数的对应值
            //lp.width: 对应于 layout_width 
            //lp.height:  对应于 layout_height 而且是已经转换了的值
		}
	}

}

转换规则:wrap_content =>WRAP_CONTENT

match_parent =>MATCH_PARENT

xxdp / xxsp >具体的像素 


利用开发者要求的尺寸结合自己的可用空间来计算出对子View的高度和宽的的限制:

所谓结合自己的可用空间来计算可以根据layout_width或layout_height的值分成三种情况:

  1. 固定值,也就是多少dp或sp:这个很好分析,因为开发者对子View已经有了死要求,给出了精确的尺寸,那么就不需要考虑可用空间的问题了直接用(mode)EXACTLY把子View的尺寸限制为这个固定值就可以了
  2. match_parent(填满父控件):因为是填满,所以原则上也是把子View的尺寸限制为固定值,这个值就是自己的可用宽度和可用高度----可用宽度和可用高度如何获取? 对子View的测量,发生在父 View的onMeasure()方法里,这个时候,自己的尺寸还没确定,只能得到可用高度和可用宽度,也就是可用空间; 怎么获取可用空间呢?

       可用空间是从自己的onMeasure()方法的两个参数--宽度限制和高度限制里获得,虽然不知道多大,但是可以算出来有多大的空间给子View去用,可用空间根据MeasureSpec的mode分为两种情况 :

  1. EXACTLY/AT_MOST:对于EXACTLY这种mode,可用宽度就是MeasureSpec的size;而AT_MOST和EXACTLY的处理方法完全一样,虽然只是一个上限,是一个动态不固定的,但是子View在用的时候应该是父View这块可用空间随便用的原则,所以可用空间也是自己的尺寸上限 它们只在测量完子View再测量自己的时候有区别:EXACTLY是固定值,AT_MOST是尺寸上限
  2. UNSPECIFIED:这个mode表示Layout的父View对自己没有尺寸限制,也就是可用空间无穷大,那它计算子View的MeasureSpec的时候就不用结合可用空间计算,只考虑子View的layout_width和layout_heigh 会出现一个疑惑:开发者要求填满父控件,而父控件的可用空间是无穷大,很明显不能这样做 解决方法:给子View也传一个UNSPECIFIED,这里就没办法满足开发者的要求,只能随便去量了

   3. wrap_content:(让子View自适应或者是说自己测量)不限制子View,但是并不相当于         直接给子View一个UNSPECIFIED,还有一个隐藏条件——不能超过父View的边界即要         在父View的可用空间内,所以也要根据MeasureSpec的mode把可用空间分成两种情况

  1.  有上限(EXACTLY/AT_MOST):初始值就是MeasureSpec的size,随着子View增加会逐渐减小,和match_parent一样,但是它的mode和match_parent不一样,wrap_content时给子View的mode是AT_MOST,因为wrap_content原则上还是让子View自己测量,只是不能超过可用空间
  2. 没上限(UNSPECIFIED):因为开发者对子View的大小没限制,父View给子View的空间是无限大,那么直接给子View的mode选为UNSPECIFIED就好了,这个和match_parent一样,但是原因不一样:match_parent是因为无穷大的可用空间没法填满,只好传UNSPECIFIED        
public class SomeLayout extends ViewGroup {


	@0verride
	protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
		for (int i = 0; i < getChildCount();i++){
			View childView = getChildAt(i);
			LayoutParams lp = childView.getLayoutParams();
            //最多是多少看MeasureSpec的mode
            int selfwidthSpecMode = MeasureSpec. getMode(widthMeasureSpec);
            int selfWidthSpecSize = MeasureSpec. getSize(widthMeasureSpec);
            swith(lp.width){
                case MATCH_PARENT:
                	if(selfwidthSpecMode == EXACTLY || selfwidthSpecMode == AT_MOST){
                        //可用空间会改变,初始可用空间就是MeasureSpec的size,子View就应被固定为这个可用空间的大小,随着子View越来越多,可用空间也就越来越小   
                        childwidthSpec = MeasureSpec.makeMeasureSpec(selfWidthSpecSize - usedWidth,EXACTLY);
                    }else {
                        //size传0即可,因为对于UNSPECIFIED这种mode来说size它没用
                        childwidthSpec = MeasureSpec.makeMeasureSpec(0,UNSPECIFIED);
                    }
                case WRAP_CONTENT:
                	if(selfwidthSpecMode == EXACTLY || selfwidthSpecMode == AT_MOST){   
                        childwidthSpec = MeasureSpec.makeMeasureSpec(selfWidthSpecSize - usedWidth,AT_MOST);
                    }else {
                        childwidthSpec = MeasureSpec.makeMeasureSpec(0,UNSPECIFIED);
                    }
                	break;
                default:
                	//返回值就是压缩后的尺寸限制
                	childwidthSpec = MeasureSpec.makeMeasureSpec(lp.width,EXACTLY);//两个参数分别是size和mode
                	break;
            }
		}
	}

}

         1.1.2 根据子View给出的尺寸,得出子View 的位置,并保存它们的位置和尺寸

测量子View的过程就是一个排版过程,在测量过程中能够拿到每一个子View的位置然后保存就好

对于尺寸,绝大多数情况来说每一个子View它所测量的尺寸就是它最后的尺寸,用的时候调用getMeasureWidth()和getMeasureHeight()即可

为什么要保存呢?因为当前还是测量阶段,在接下来的布局阶段,在onLayout()方法里,这些位置和尺寸才会传给子View,关于这里补充两点:

  1. 并不是所有的Layout都需要去保存子View的位置,有的Layout可以在布局阶段实时推导出子View的位置(比如LinearLayout,它的布局全部是横向或者纵向排列,位置可以由尺寸大小一个一个累加得到) 但是具体的还要看具体要求
  2. 有时候对某些子View需要重复测量两次或多次才能得到正确的尺寸和位置 可能会需要给出新的MeasureSpec来让子View重新测量,知道达到要求         

        1.1.3 根据子View 的位置和尺寸计算出自己的尺寸,并用setMeasuredDimension()保存

根据子View的排版来计算边界,边界就是要求的尺寸了

 1.2 重写onLayout()来摆放子View

        1.2.1 自我布局

只需要做一件事,那就是调用每一个子View的onLayout()把之前在onMeasure()中保存下来的尺寸和位置作为参数传进去,让它们把自己的位置和尺寸保存下来,并进行自我布局

@0verride
protected void onLayout ( boolean changed,int l,int t, int r, int b){
    for (int i = 0; i < getChildCount(); i++){
        View childView =getChildAt(i);
        childView.layout(childLeft[i], childTop[i], childRight[i],childBottom[i]);
    }
}

子View的layout方法中四个参数是他在父View中的相对坐标,需要把位置和尺寸转换一下,转换成左上右下  

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值