常用的自定义View例子一(流布式布局)

public SimpleFlowLayout(Context context ) {

super(context);

}

/**

  • 重写onMeasure方法是为了确定最终的大小

  • @param widthMeasureSpec

  • @param heightMeasureSpec

*/

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);

int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);

int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);

int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

int paddingLeft = getPaddingLeft();

int paddingRight = getPaddingRight();

int paddingTop = getPaddingTop();

int paddingBottom = getPaddingBottom();

//处理Padding属性,让当前的ViewGroup支持Padding

int widthUsed = paddingLeft + paddingRight;

int heightUsed = paddingTop + paddingBottom;

int childMaxHeightOfThisLine = 0;

int childCount = getChildCount();

for (int i = 0; i < childCount; i++) {

View child = getChildAt(i);

if (child.getVisibility() != GONE) {

// 已用的宽度

int childUsedWidth = 0;

// 已用的高度

int childUsedHeight = 0;

// 调用ViewGroup自身的方法测量孩子的宽度和高度,我们也可以自己根据MeasureMode来测量

measureChild(child,widthMeasureSpec,heightMeasureSpec);

childUsedWidth += child.getMeasuredWidth();

childUsedHeight += child.getMeasuredHeight();

//处理Margin,支持孩子的Margin属性

Rect marginRect = getMarginRect(child);

int leftMargin=marginRect.left;

int rightMargin=marginRect.right;

int topMargin=marginRect.top;

int bottomMargin=marginRect.bottom;

childUsedWidth += leftMargin + rightMargin;

childUsedHeight += topMargin + bottomMargin;

//总宽度没有超过本行

if (widthUsed + childUsedWidth < widthSpecSize) {

widthUsed += childUsedWidth;

if (childUsedHeight > childMaxHeightOfThisLine) {

childMaxHeightOfThisLine = childUsedHeight;

}

} else {//总宽度已经超过本行

heightUsed += childMaxHeightOfThisLine + verticalSpacing;

widthUsed = paddingLeft + paddingRight + childUsedWidth;

childMaxHeightOfThisLine = childUsedHeight;

}

}

}

//加上最后一行的最大高度

heightUsed += childMaxHeightOfThisLine;

setMeasuredDimension(widthSpecSize, heightUsed);

}

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

int paddingLeft = getPaddingLeft();

int paddingRight = getPaddingRight();

int paddingTop = getPaddingTop();

int paddingBottom = getPaddingBottom();

/**

  • 为了 支持Padding属性

*/

int childStartLayoutX = paddingLeft;

int childStartLayoutY = paddingTop;

int widthUsed = paddingLeft + paddingRight;

int childMaxHeight = 0;

int childCount = getChildCount();

//摆放每一个孩子的高度

for (int i = 0; i < childCount; i++) {

View child = getChildAt(i);

if (child.getVisibility() != GONE) {

int childNeededWidth, childNeedHeight;

int left, top, right, bottom;

int childMeasuredWidth = child.getMeasuredWidth();

int childMeasuredHeight = child.getMeasuredHeight();

Rect marginRect = getMarginRect(child);

int leftMargin=marginRect.left;

int rightMargin=marginRect.right;

int topMargin=marginRect.top;

int bottomMargin=marginRect.bottom;

childNeededWidth = leftMargin + rightMargin + childMeasuredWidth;

childNeedHeight = topMargin + topMargin + childMeasuredHeight;

// 没有超过本行

if (widthUsed + childNeededWidth <= r - l) {

if (childNeedHeight > childMaxHeight) {

childMaxHeight = childNeedHeight;

}

left = childStartLayoutX + leftMargin;

top = childStartLayoutY + topMargin;

right = left + childMeasuredWidth;

bottom = top + childMeasuredHeight;

widthUsed += childNeededWidth;

childStartLayoutX += childNeededWidth;

} else {

childStartLayoutY += childMaxHeight + verticalSpacing;

childStartLayoutX = paddingLeft;

widthUsed = paddingLeft + paddingRight;

left = childStartLayoutX + leftMargin;

top = childStartLayoutY + topMargin;

right = left + childMeasuredWidth;

bottom = top + childMeasuredHeight;

widthUsed += childNeededWidth;

childStartLayoutX += childNeededWidth;

childMaxHeight = childNeedHeight;

}

child.layout(left, top, right, bottom);

}

}

}

private Rect getMarginRect(View child) {

LayoutParams layoutParams = child.getLayoutParams();

int leftMargin = 0;

int rightMargin = 0;

int topMargin = 0;

int bottomMargin = 0;

if (layoutParams instanceof MarginLayoutParams) {

MarginLayoutParams marginLayoutParams = (MarginLayoutParams) layoutParams;

leftMargin = marginLayoutParams.leftMargin;

rightMargin = marginLayoutParams.rightMargin;

topMargin = marginLayoutParams.topMargin;

bottomMargin = marginLayoutParams.bottomMargin;

}

return new Rect(leftMargin, topMargin, rightMargin, bottomMargin);

}

}

2)思路解析

  1. 首先我们重写onMeasure方法,在OnMeasure方法里面我们调用measureChild()这个方法去获取每个孩子的宽度和高度,每次增加一个孩子我们执行 widthUsed += childUsedWidth;

  2. 添加完一个孩子以后我们判断widthUsed是够超出控件本身的最大宽度widthSpecSize,

若没有超过执行

widthUsed += childUsedWidth;

if (childUsedHeight > childMaxHeightOfThisLine) {

childMaxHeightOfThisLine = childUsedHeight;

}

超过控件的宽度执行

heightUsed += childMaxHeightOfThisLine + verticalSpacing;

widthUsed = paddingLeft + paddingRight + childUsedWidth;

childMaxHeightOfThisLine = childUsedHeight;

最后调用 setMeasuredDimension(widthSpecSize, heightUsed);这个方法去设置它的大小

3.在OnLayout方法里面,所做的工作就是去摆放每一个孩子的位置 ,判断需不需要换行,不需要更改left值,需要换行,更改top值

3)注意事项

讲解之前,我们先来了解一下一个基本知识

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


从这张图片里面我们可以得出这样结论

  1. Width=控件真正的宽度(realWidth)+PaddingLeft+PaddingRight

  2. margin是子控件相对于父控件的距离

注意事项

  1. 为了支持控件本身的padding属性,我们做了处理,主要代码如下

int widthUsed = paddingLeft + paddingRight;

int heightUsed = paddingTop + paddingBottom;


if (widthUsed + childUsedWidth < widthSpecSize) {

widthUsed += childUsedWidth;

if (childUsedHeight > childMaxHeightOfThisLine) {

childMaxHeightOfThisLine = childUsedHeight;

}

}

  1. 为了支持子控件的margin属性,我们同样也做了处理

Rect marginRect = getMarginRect(child);

int leftMargin=marginRect.left;

int rightMargin=marginRect.right;

int topMargin=marginRect.top;

int bottomMargin=marginRect.bottom;

childUsedWidth += leftMargin + rightMargin;

childUsedHeight += topMargin + bottomMargin;

即我们在计算孩子所占用的宽度和高度的时候加上margin属性的高度,接着在计算需要孩子总共用的宽高度的时候加上每个孩子的margin属性的宽高度,这样自然就支持了孩子的margin属性了

4.缺陷

如下图所见,在控件宽度参差不齐的情况下,控件换行会留下一些剩余的宽度,作为想写出鲁棒性的代码的我们会觉得别扭,于是我们相处了解决办法。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

解决方法见下面

图二源码解析


外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

废话不多说,先看源码

/**

  • 博客地址:http://blog.youkuaiyun.com/gdutxiaoxu

  • @author xujun

  • @time 2016/6/26 22:54.

*/

public class PrefectFlowLayout extends ViewGroup {

public PrefectFlowLayout(Context context) {

super(context);

}

public PrefectFlowLayout(Context context, AttributeSet attrs) {

super(context, attrs);

}

public PrefectFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

}

//父容器宽度

private int parentWidthSize;

//水平间距

private int horizontalSpacing = 16;

//垂直间距

private int verticalSpacing = 16;

//当前行

private Line currentLine;

//所有行的集合

private List mLines = new ArrayList<>();

//当前行已使用宽度

private int userWidth = 0;

/**

  • 行对象

*/

private class Line {

//一行里面所添加的子View集合

private List children;

//当前行高度

private int height;

//当前行已使用宽度

private int lineWidth = 0;

public Line() {

children = new ArrayList<>();

}

/**

  • 添加一个子控件

  • @param child

*/

private void addChild(View child) {

children.add(child);

if (child.getMeasuredHeight() > height) {

//当前行高度以子控件最大高度为准

height = child.getMeasuredHeight();

}

//将每个子控件宽度进行累加,记录使用的宽度

lineWidth += child.getMeasuredWidth();

}

/**

  • 获取行的高度

  • @return

*/

public int getHeight() {

return height;

}

/**

  • 获取子控件的数量

  • @return

*/

public int getChildCount() {

return children.size();

}

/**

  • 放置每一行里面的子控件的位置

  • @param l 距离最左边的距离

  • @param t 距离最顶端的距离

*/

public void onLayout(int l, int t) {

//当前行使用的宽度,等于每个子控件宽度之和+子控件之间的水平距离

lineWidth += horizontalSpacing * (children.size() - 1);

int surplusChild = 0;

int surplus = parentWidthSize - lineWidth;//剩余宽度

if (surplus > 0) {

//如果有剩余宽度,则将剩余宽度平分给每一个子控件

surplusChild = (int) (surplus / children.size()+0.5);

}

for (int i = 0; i < children.size(); i++) {

View child = children.get(i);

child.getLayoutParams().width=child.getMeasuredWidth()+surplusChild;

if (surplusChild>0){

//如果长度改变了后,需要重新测量,否则布局中的属性大小还会是原来的大小

child.measure(MeasureSpec.makeMeasureSpec(

child.getMeasuredWidth()+surplusChild,MeasureSpec.EXACTLY)

,MeasureSpec.makeMeasureSpec(height,MeasureSpec.EXACTLY));

}

child.layout(l, t, l + child.getMeasuredWidth(), t + child.getMeasuredHeight());

l += child.getMeasuredWidth() + horizontalSpacing;

}

}

}

// getMeasuredWidth() 控件实际的大小

// getWidth() 控件显示的大小

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

//将之前测量的数据进行清空,以防复用时影响下次测量

mLines.clear();

currentLine = null;

userWidth = 0;

//获取父容器的宽度和模式

int widthMode = MeasureSpec.getMode(widthMeasureSpec);

parentWidthSize = MeasureSpec.getSize(widthMeasureSpec)

  • getPaddingLeft() - getPaddingRight();

//获取父容器的高度和模式

int heigthMode = MeasureSpec.getMode(heightMeasureSpec);

int heightSize = MeasureSpec.getSize(heightMeasureSpec)

  • getPaddingTop() - getPaddingBottom();

int childWidthMode, childHeightMode;

//为了测量每个子控件,需要指定每个子控件的测量规则

//子控件设置为WRAP_CONTENT,具体测量规则详见,ViewGroup的getChildMeasureSpec()方法

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

//子控件设置为WRAP_CONTENT,具体测量规则详见,ViewGroup的getChildMeasureSpec()方法

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-2uwtTRZA-1715902119424)]

[外链图片转存中…(img-gjSULTCM-1715902119425)]

[外链图片转存中…(img-z0K6HEgT-1715902119426)]

[外链图片转存中…(img-po80SjYK-1715902119427)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值