Android绘制——自定义view之onLayout
创始人
2025-05-31 08:18:07
0

简介

在自定义view的时候,其实很简单,只需要知道3步骤:

  • 测量——onMeasure():决定View的大小,关于此请阅读《Android自定义控件之onMeasure》
  • 布局——onLayout():决定View在ViewGroup中的位置
  • 绘制——onDraw():如何绘制这个View。

View视图结构

View视图可以是单一的一个如TextView,也可以是一个视图组(ViewGroup)如LinearLayout。

如图:对于多View的视图他的结构是树形结构,最顶层是ViewGroup,ViewGroup下可能有多个ViewGroup或View。

这个树的概念很重要,因为无论我们是在测量大小或是调整布局的时候都是从树的顶端开始一层一层,一个分支一个分支的进行(树形递归)。

onLayout函数

measure的作用就是为整个View树计算实际的大小,而通过刚才对View树的介绍知道,想计算整个View树的大小,就需要递归的去计算每一个子视图的大小(Layout同理)。

对每一个视图通过onMeasure方法的一系列测量流程后计算出实际的高(mMeasuredHeight)和宽(mMeasureWidth)传入setMeasuredDimension()方法完成单个View的测量,如果所测的视图是ViewGroup则可以通过measureChild方法递归的计算其中的每一个子view。对于每个View的实际宽高都是由父视图和本身视图决定的。

Layout的作用就是为整个View树计算实际的位置,而通过刚才对View树的介绍知道,想计算整个View树的位置,就需要递归的去计算每一个子视图的位置(Measure同理)。

而确定这个位置很简单,只需要mLeft,mTop,mRight,mBottom四个值(注意:这4个值是子View相对于父View的值,下面会详细介绍)。

在代码中如何设置这4个值呢?

首先,无论是系统提供的LinearLayout还是我们自定义的View视图,他都需要继承自ViewGroup类,之后必须要做的就是重写onLayout方法(因为在onLayout在ViewGroup中被定义为抽象方法)。

自定义view—onLayout

view类的onLayout()是个空方法

viewGroup的onLayout()是个抽象方法

layou()中的onLayout() 是用来设置viewgroup中子view的位置的 ,而不是用来设置当前view的位置的

/** * 存储所有的View,按行记录 */

private List> mAllViews = new ArrayList>()

/** * 记录每一行的最大高度 */

private ListmLineHeight = new ArrayList();
​
@Override
​
protected void onLayout(boolean changed, int l, int t, int r, int b)
​
{
​
mAllViews.clear();
​
mLineHeight.clear();
​
int width = getWidth();
​
int lineWidth = 0;    // 记录每一行 每加入一个子view之后的当前行宽
​
int lineHeight = 0 ;  // 记录每一行 每加入一个子view之后的当前行高(取最大值)
​
ListlineViews = new ArrayList();
​
int cCount = getChildCount();
​
// 遍历所有的孩子     
​
for (int i = 0; i < cCount; i++)     {       
​
View child = getChildAt(i); 
​
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();    
​int childWidth = child.getMeasuredWidth();      
​
int childHeight = child.getMeasuredHeight();        
​
// 如果已经需要换行     
​
if (childWidth + lp.leftMargin + lp.rightMargin + lineWidth > width) {    
​
// 记录这一行所有的View以及最大高度  
​
mLineHeight.add(lineHeight);
​// 将当前行的childView保存,然后开启新的ArrayList保存下一行的childView         mAllViews.add(lineViews);         
​
lineWidth = 0;// 重置行宽         
​
lineViews = new ArrayList();
​
}
​
/**
​
* 如果不需要换行,则累加
​
*/
​
lineWidth += childWidth + lp.leftMargin + lp.rightMargin;                    
​
lineHeight = Math.max(lineHeight, childHeight + lp.topMargin+ lp.bottomMargin);
​
lineViews.add(child);
​
}
​
// 记录最后一行
​
mLineHeight.add(lineHeight);
​
mAllViews.add(lineViews);
​
此循环小结
​
// 获取到所有的子view 以及子view的Marginlayoutparams
​
// 根据当前子view的宽度左右margin 以及当前行的lineWindth 判断是否换行
​
// 如果换行 则 将行高加入保存下来 并重置行宽行高以及行集合
​
// 并将行集合保存到总集合之中
​
// 如果不换行 则记录下当前行的行宽行高 并将当前view加入行集合
​
// 遍历完所有的集合之后将行高与行集合分别保存下来
​
// (因为遍历完所有的子view之后,最后一行肯定是不换行,所以行高和行集合都没有保存)
​
int left = 0;
​
int top = 0; 
​
// 得到总行数
​
int lineNums = mAllViews.size();
​
for (int i = 0; i < lineNums; i++)
​
{
​
// 每一行的所有的views
​
lineViews = mAllViews.get(i);
​
// 当前行的最大高度
​
lineHeight = mLineHeight.get(i);
​
Log.e(TAG, "第" + i + "行 :" + lineViews.size() + " , " + lineViews);
​
Log.e(TAG, "第" + i + "行, :" + lineHeight);
​
// 遍历当前行所有的View
​
for (int j = 0; j < lineViews.size(); j++)
​
{
​
View child = lineViews.get(j);
​
if (child.getVisibility() == View.GONE)
​
{
​
continue;
​
}
​
MarginLayoutParams lp = (MarginLayoutParams) child
​
.getLayoutParams();
​
//计算childView的left,top,right,bottom
​
int lc = left + lp.leftMargin;     左                   
​
int tc = top + lp.topMargin;       上
​
int rc =lc + child.getMeasuredWidth();  右
​
int bc = tc + child.getMeasuredHeight();  下
​
Log.e(TAG, child + " , l = " + lc + " , t = " + t + " , r ="
​
+ rc + " , b = " + bc);
​
child.layout(lc, tc, rc, bc);
​
left += child.getMeasuredWidth() + lp.rightMargin
​
+ lp.leftMargin;
​
}
​
left = 0;
​
top += lineHeight;
​
}
​
}

此循环小结

之后遍历总集合 得到行集合 然后根据相应的下标获取到每一行的行高遍历行集合 得到每一行的子view 然后获取每个子view的 左上坐标 右下坐标 然后调用子view的layout()获取子view的左坐标 初始left为0 每次计算完之后 将当前view的宽度相加最后设置每个子view的layout() 。

相关内容

热门资讯

50万亿天量存款到期,银行理财... 来源:21世纪经济报道 “三年前存的三年期定存,年利率还有3%以上,现在到期后再存,利息直接少了一半...
全年35场特色活动!老字号嘉年... 1月26日,商务部联合广东省人民政府举办的全国老字号嘉年华暨大湾区年味消费季启动仪式在广州市荔湾区陈...
日媒:“日美协同干预”传闻致日... 参考消息网1月27日报道 据《日本经济新闻》网站1月26日报道,在周一(26日)的外汇市场上,日元汇...
凯德北京投资基金管理有限公司:... 全球存储芯片巨头美光科技本周一宣布,其位于新加坡现有制造园区内的全新先进晶圆制造厂已正式破土动工。这...
欠钱不还,还动手打人,这名国企... 这件事太让人意想不到,也太不可思议。 欠债还钱,天经地义。但凡懂得一点道理的,都知道这句话的含义,也...
超20家银行科技条线“掌舵者”... 近年来,数字化转型已成为银行高质量发展的核心引擎。作为科技条线的“掌舵者”,首席信息官(CIO)不仅...
武汉病毒所发现尼帕有效药物!生... 今日(1.27),生物疫苗题材回调,生物医药ETF汇添富(159839)收跌2.08%,成交额保持高...
金价飙新高,A股炒贵金属板块自... 最近刷财经新闻,估计不少兄弟都被金价飙涨的消息炸到了——国际金价突破5100美元/盎司,创历史新高,...
侃股:重罚操纵股价,投资者持股... 自然人余韩因操纵博士眼镜股价,被证监会罚没金额合计超过10亿元,并同时被采取三年证券市场禁入措施及三...
小红书“拿下”沃尔玛,开始大批... 编辑 | 文定 当商超巨头沃尔玛遇上生活方式社区小红书,一场线上内容与线下零售的深度融合就此开启。双...
华北制药股份有限公司关于公司获... 来源:上海证券报 证券代码:600812 股票简称:华北制药 编号:临2026-004 华北制药股...
MiniMax创上市新高,市值... 来源:格隆汇APP 格隆汇1月27日|港股市场AI概念股临近尾盘集体走强,其中,MINIMAX-WP...
追觅是下一个乐视?CEO回应:... 做新能源汽车、做高端智能手机、做酒旅品牌、做航空服务…… 追觅近期不断曝出跨界开拓新业务,甚至其C...
马斯克力推太空光伏,为何看上的... 界面新闻实习记者 | 肖一凡 1月22日,马斯克在达沃斯论坛上宣布,SpaceX和特斯拉计划三年内...
1月27日连板股分析:连板股晋... 【1月27日连板股分析:连板股晋级率33% 贵金属板块分歧加剧】今日共53股涨停,连板股总数14只,...
腾讯、淡马锡等基石投资者认购1... 中国最大的休闲食品饮料连锁零售商鸣鸣很忙(01768)将于1月28日(星期三)在香港挂牌。截至发稿,...
【朝医新闻】建科即攀峰 骨肿瘤... 2025年12月1日,北京朝阳医院骨与软组织肿瘤诊疗中心(以下简称骨肿瘤中心)正式建科。建科伊始,团...
追觅CEO俞浩回应“断指计划”... 1月27日,追觅科技创始人兼CEO俞浩回应“断指计划”传闻:没有,谣传。 俞浩称,刚入职的员工,工资...
整治政府采购领域“内卷式”竞争... 为整治政府采购领域“内卷式”竞争 财政部近日发出通知 就推动解决政府采购异常低价问题 提出明确要求 ...