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() 。

相关内容

热门资讯

走进小城看消费丨江西资溪:低碳...   夏日时节下午4点,江西省抚州市资溪县大觉山景区漂流终点依然热闹。来自南昌的游客余鑫漂流结束后没有...
【中原晨会0625】市场分析专... 来源:市场资讯 (来源:中原证券研究所) 本期重点研报目录 【中原策略】市场分析:电子半导体领涨 ...
南向资金连买4日!低费率+可月... 6月25日早盘,港股红利资产震荡整理。截至11时14分,港股红利低波ETF招商(520550)下跌0...
618成交破百万!紫荆花用一套... 一年一度的618年中大促,是消费市场的晴雨表,也是品牌间最激烈的角力场。当各大品牌在直播间里铆足了劲...
原创 黄... 2026年6月25日的国际金价已经从前期的5500美元高点跌到4200美元下方,累计跌幅超过22%,...
英伟达CEO:Vera Rub... 截至9:38,中证半导体材料设备主题指数(931743)涨2.36%创新高;权重股中,中微公司涨3....
再被催债16亿!“钢铁大王”戴... 澎湃新闻记者 贺梨萍 因“铁本事件”入狱五年的戴国芳重返钢铁行业,但他并没有完成从阶下囚再到“钢铁大...
周三原油价格下跌 随着美国和伊朗在和平谈判中取得进展,越来越多的油轮公开穿越霍尔木兹海峡,原油在战时的价格上涨已经蒸发...
这种蛋白是大脑衰老的开关 这种蛋白是大脑衰老的开关 清晨,假设一位五十岁左右的王女士发现自己常常把手机放在熟悉的抽屉里又找不到...
信通院牵头算力Token出海生... 盘面上,截至11:04,中证科创创业50指数(931643)涨1.68%,创历史新高;权重股中,芯原...
海外 774 亿营收背后:日本... 文 | 游戏价值论 6月23日,彭博社报道了腾讯正在围绕出售多家日本游戏工作室少数股权开展谈判,包...
餐饮“抢人”大战:把店开到公交... 作者 |餐饮老板内参 内参君 医院、公交站、演唱会…餐饮品牌,正在无孔不入 在北京儿童医院,肯德基...
快讯 | 外资扫货!陈翊庭:港... 港交所行政总裁陈翊庭在接受《中国证券报》专访时指出,国际资本对中国资产的看法已彻底扭转,布局中国市场...
2777.77元!A股“股王”... 25日早盘,昨天创下历史新高的A股“股王”联讯仪器,今天上午继续走强,盘中股价再度刷新历史新高。 截...
原创 今... 欧洲自己的媒体直接下结论,欧盟衰退躲不掉,内部分裂拦不住,现在就连欧洲顶尖工业巨头,都偷偷在用中国的...
黄仁勋股东大会放言:本轮AI基... 在当地时间6月24日的英伟达(NVDA.O)2026年度股东大会上,股东批准了该公司全部10名董事会...
国际油价大跌 新华社消息, 纽约原油期货主力合约价格24日盘中跌破每桶70美元,为伊朗战事爆发以来首次。 市场分析...
马云带队插秧,什么信号? 一场别开生面的“务农”,让外界看到了一个不一样的阿里巴巴。 近日,阿里巴巴合伙人、高德董事长刘振飞在...
全球最大产能,最高丰度达99.... 本文转自【科技日报】; 6月23日,高丰度硼-10同位素技术暨产业化成果发布会在山东省东营市举办,全...
黄金大跳水!金饰克价年内暴跌近... 25日,现货黄金盘中震荡,截至发稿,报3985.070美元/盎司,跌0.17%。 当地时间24日,...