Android自定义控件开发入门与实战(16)封装控件

网友投稿 700 2022-09-08

Android自定义控件开发入门与实战(16)封装控件

Android自定义控件开发入门与实战(16)封装控件

第十二章、封装控件

实习以来已经写许多个自定义控件了,所以这章有很多知识我就不想写了。如果有人想了解就自己上网找吧。

1、自定义属性与自定义Style

不讲了,就是给自定义的控件添加自定义属性。 步骤大概就是

2、测量与布局

View和ViewGroup的绘制流程的三大流程 balabalabala。。。

在给Viewgroup加margin时,如果在onLayout()里面加,那就也要在onMeasure()里加。否则会导致Container太小而控件显示不全的问题。

我们可以重写 generateLayoueParams()和generateDefaultLayoutParams()函数,在里面直接返回MarginLayoutParams()

只有重写generateLayoueParams()函数才能获取到控件的margin值。 这是因为在contain中初始化子控件时,会调用generateLayoueParams-来为子控件生成对应的布局属性,但默认只生成layout_width和layout_height,即正常情况下generateLayoueParams()得到的LayoutParams是得不到margin值的。而MarginLayoutParams是派生自LayoutParams的,所以我们在LayoutParams强转MarginLayoutParams是不会报错的。

3、实现FlowLayout容器

首先,这个FlowLayout中有多个TextView,我们在style中定义他们:

然后xml如下:

。。。

这里注意:我们为FlowLayout的宽度设为match_parent,高度设为wrap_content

1、提取margin值与重写onMeasure 我们要提取margin值,就一定要重写generateLayout()函数:

@Override protected LayoutParams generateLayoutParams(LayoutParams p) { return new MarginLayoutParams(p); } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs); } @Override protected LayoutParams generateDefaultLayoutParams() { return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); }

然后重写onMeasure()函数去计算当前FlowLayout所占的区域大小。 要实现FlowLayout,必然涉及下面几个问题: (1)什么时候换行: FlowLayout是一行行的,所以当前行已经放不下下一控件时,就将该控件放到下一行显示。所以需要一个变量来计算当前行已经占据的宽度,以判断剩下的空间是否还能容得下下一个控件 (2)如何得到FlowLayout宽度 宽度是所有行宽度的最大值,所以我们每记录一行的时候就要将做最大行宽度的比较 (3)如何得到FLowLayout高度 高度是每一行总和,每一行的高度是其中控件高度的最大值。

我们先利用MeasureSpec获取系统建议的数值和模式。

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int measureWidth = MeasureSpec.getSize(widthMeasureSpec); int measureHeight = MeasureSpec.getSize(heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec);}

然后计算FlowLayout所占用区域的大小,先定义几个变量:

//记录每一行的宽度 int lineWidth = 0; //记录每一行的高度 int lineHeight = 0; //记录整个Layout的高度 int height = 0; //记录整个Layout的宽度 int width = 0;

再开始计算:

int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); measureChild(child, widthMeasureSpec, heightMeasureSpec); MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; int childHeight = child.getMeasuredHeight() + lp-Margin + lp.bottomMargin; if (lineWidth + childWidth > measureWidth) { //需要换行 height += lineHeight; //因为当前行放不下当前空间,而将此控件调到下一行,所以将此控件的高度和宽度初始化给lineHeight、lineWidth lineHeight = childHeight; lineWidth = childWidth; } else { //否则累加lineWidh,lineHeight取最大值 lineHeight = Math.max(lineHeight, childHeight); lineWidth += childWidth; } //因为最后一行是不会超出width范围的,所以需要单独处理 if (i == count - 1) { height += lineHeight; width = Math.max(width, lineWidth); } } setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? measureWidth : width, heightMode == MeasureSpec.EXACTLY ? measureHeight : height);

上面代码中: 我们开启循环遍历子View并用下面代码:

= getChildAt(i); measureChild(child, widthMeasureSpec, heightMeasureSpec); MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; int childHeight = child.getMeasuredHeight() + lp-Margin + lp.bottomMargin;

来获取和计算每个子View的宽高。 然后用通过该子View的宽高,来计算FlowLayout的宽高和要不要换行。

if (lineWidth + childWidth > measureWidth) { //需要换行 height += lineHeight; //因为当前行放不下当前空间,而将此控件调到下一行,所以将此控件的高度和宽度初始化给lineHeight、lineWidth lineHeight = childHeight; lineWidth = childWidth; } else { //否则累加lineWidh,lineHeight取最大值 lineHeight = Math.max(lineHeight, childHeight); lineWidth += childWidth; }

需要注意的是,在计算最后一行时,肯定是不会超过行宽的,而我们在for循环中,当不超过行宽时只做了如下处理:

} else { //否则累加lineWidh,lineHeight取最大值 lineHeight = Math.max(lineHeight, childHeight); lineWidth += childWidth; }

我们只计算了行高和行宽,没有记录整个layout的宽和高 所以到了最后一个控件的时候,我们需要单独计算width 和height

//因为最后一行是不会超出width范围的,所以需要单独处理 if (i == count - 1) { height += lineHeight; width = Math.max(width, lineWidth); }

2、重写onLayout函数 我们要一个个布局子控件,由于控件要后移和换行,所以我们要标记当前空间的top坐标和left坐标。 我们先申请下面几个变量。

@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int count = getChildCount(); //累加当前行的行宽 int lineWidth = 0; //当前行的行高 int lineHeight = 0; //当前控件的top坐标和left坐标 int top = 0, left = 0;}

然后计算每个控件的top坐标和left坐标,再调用layout(l,t,r,b)

for (int i = 0; i < count; i++) { View child = getChildAt(i); MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; int childHeight = child.getMeasuredHeight() + lp-Margin + lp.bottomMargin; if (childWidth + lineWidth > getMeasuredWidth()) { //换行 top += lineHeight; left = 0; lineHeight = childHeight; lineWidth = childWidth; } else { lineHeight = Math.max(lineHeight, childHeight); lineWidth += childWidth; } int lc = left + lp.leftMargin; int tc = top + lp-Margin; int rc = lc + child.getMeasuredWidth(); int bc = tc + child.getMeasuredHeight(); child.layout(lc, tc, rc, bc); //将left置为下一个子控件的起始点 left += childWidth; }

其中,前面的获取子View的宽高在onMeasure方法中也用过,不过我们不用在调用 measureChild(child, widthMeasureSpec, heightMeasureSpec)了,因为已经measure完了。 然后我们计算每个控件的top和left坐标,再调用layout方法来布局每个控件

if (childWidth + lineWidth > getMeasuredWidth()) { //换行 top += lineHeight; left = 0; lineHeight = childHeight; lineWidth = childWidth; } else { lineHeight = Math.max(lineHeight, childHeight); lineWidth += childWidth; } int lc = left + lp.leftMargin; int tc = top + lp-Margin; int rc = lc + child.getMeasuredWidth(); int bc = tc + child.getMeasuredHeight(); child.layout(lc, tc, rc, bc); //将left置为下一个子控件的起始点 left += childWidth;

如果当前控件已经塞不下剩下空间了,就换行,然后 top加上之前的行高,left为0(从最左算起) (这里计算的都是不带margin的) 如果当前行还能容得下该子控件,则计算下行高和行长度,然后 left坐标是 left+左margin。。。。 然后layout一遍,left坐标加上本子View宽度,到下一个控件的起始left坐标。

到这里为止, FlowLayout就实现了。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:两个很实用的Python装饰器详解(python中的装饰器)
下一篇:hdoj 5818 5821
相关文章

 发表评论

暂时没有评论,来抢沙发吧~