android 自定义viewgroup onmeasure,一篇文章搞懂Android 自定义Viewgroup的难点
本文的目的
目的在于教会大家到底如何自定义viewgroup,自定义布局和自定义测量到底如何写。很多网上随便搜搜的概念和流程图这里不再过多描述了,建议大家看本文之前,先看看基本的自定义viewgroup流程,心中有个大概即可。本文注重于实践。
viewgroup 的测量布局流程基本梳理
稍微回顾下,基本的viewgroup绘制和布局流程中的重点:
view 在onMeasure()方法中进行自我测量和保存,也就是说对于view(不是viewgroup噢)来说一定在onMeasure方法中计算出自己的尺寸并且保存下来
viewgroup实际上最终也是循环从上大小来调用子view的measure方法,注意子view的measure其实最终调用的是子view的onMeasure 方法。所以我们理解这个过程为: viewgroup循环遍历调用所有子view的onmeasure方法,利用onmeasure方法计算出来的大小,来确定这些子view最终可以占用的大小和所处的布局的位置。
measure方法是一个final方法,可以理解为做测量工作准备工作的,既然是final方法所以我们无法重写它,不需要过多关注他,因为measure最终要调用onmeasure ,这个onmeasure我们是可以重写的。要关注这个。layout和onlayout是一样的关系。
父view调用子view的layout方法的时候会把之前measure阶段确定的位置和大小都传递给子view。
对于自定义view/viewgroup来说 我们几乎只需要关注下面三种需求:
对于已有的android自带的view,我们只需要重写他的onMeasure方法即可。修改一下这个尺寸即可完成需求。
对于android系统没有的,属于我们自定义的view,比上面那个要复杂一点,要完全重写onMeasure方法。
第三种最复杂,需要重写onmeasure和onlayout2个方法,来完成一个复杂viewgroup的测量和布局。
onMeasure方法的特殊说明:
如何理解父view对子view的限制?
onMeasure的两个参数既然是父view对子view的限制,那么这个限制的值到底是哪来的呢?
实际上,父view对子view的限制绝大多数就来自于我们开发者所设置的layout开头的这些属性
比方说我们给一个imageview设置了他的layout_width和layout_height 这2个属性,那这2个属性其实就是我们开发者所期望的宽高属性,但是要注意了, 设置的这2个属性是给父view看的,实际上对于绝大多数的layout开头的属性这些属性都是设置给父view看的
为什么要给父view看?因为父view要知道这些属性以后才知道要对子view的测量加以什么限制?
到底是不限制(UNSPECIFIED)?还是限制个***值(AT_MOST),让子view不超过这个值?还是直接限制死,我让你是多少就得是多少(EXACTLY)。
自定义一个BannerImageView 修改onMeasure方法
所谓bannerImageview,就是很多电商其实都会放广告图,这个广告图的宽高比都是可变的,我们在日常开发过程中也会经常接触到这种需求:imageview的宽高比 在高保真中都标注出来,但是考虑到很多手机的屏幕宽度或者高度都不确定所以我们通常都要手动来计算出这个imageview高度或者宽度,然后动态改变width或者height的值。这种方法可用但是很麻烦这里给出一个自定义的imageview,通过设置一个ratio的属性即可动态的设置iv的高度。很是方便
看下效果
***看下代码,重要的部分都写在注释里了,不再过多讲了。
publicclass BannerImageView extends ImageView {
//宽高比
floatratio;
publicBannerImageView(Context context) {
super(context);
}
publicBannerImageView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.BannerImageView);
ratio = typedArray.getFloat(R.styleable.BannerImageView_ratio, 1.0f);
typedArray.recycle();
}
publicBannerImageView(Context context, AttributeSet attrs,intdefStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(intwidthMeasureSpec,intheightMeasureSpec) {
//人家自己的测量还是要自己走一遍的,因为这个方法内部会调用setMeasuredDimension方法来保存测量结果了
//只有保存了以后 我们才能取得这个测量结果 否则你下面是取不到的
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//取测量结果
intmWidth = getMeasuredWidth();
intmHeight = (int) (mWidth * ratio);
//保存了以后,父view就可以拿到这个测量的宽高了。不保存是拿不到的噢。
setMeasuredDimension(mWidth, mHeight);
}
}
自定义view,完全自己写onMeasure方法
首先明确一个结论:
对于完全自定义的view,完全自己写的onMeasure方法来说,你保存的宽高必须要符合父view的限制,否则会发生bug,保存父view对子view的限制的方法也很简单直接调用resolveSize方法即可。
所以对于完全自定义的view onMeasure方法也不难写了,
先算自己想要的宽高,比如你画了个圆,那么宽高就肯定是半径的两倍大小, 要是圆下面还有字,那么高度肯定除了半径的两倍还要有字体的大小。对吧。很简单。这个纯看你自定义view是啥样的
算完自己想要的宽高以后 直接拿resolveSize 方法处理一下 即可。
***setMeasuredDimension 保存。
范例:
publicclass LoadingView extendsView{
//圆形的半径
intradius;
//圆形外部矩形rect的起点
intleft= 10,top= 30;
Paint mPaint = new Paint();
publicLoadingView(Context context) {
super(context);
}
publicLoadingView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LoadingView);
radius = typedArray.getInt(R.styleable.LoadingView_radius, 0);
}
publicLoadingView(Context context, AttributeSet attrs,intdefStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(intwidthMeasureSpec,intheightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
intwidth =left+ radius * 2;
intheight =top+ radius * 2;
//一定要用resolveSize方法来格式化一下你的view宽高噢,否则遇到某些layout的时候一定会出现奇怪的bug的。
//因为不用这个 你就完全没有父view的感受了 ***强调一遍
width = resolveSize(width, widthMeasureSpec);
height = resolveSize(height, heightMeasureSpec);
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
RectF oval = new RectF(left,top,
left+ radius * 2,top+ radius * 2);
mPaint.setColor(Color.BLUE);
canvas.drawRect(oval, mPaint);
//先画圆弧
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(2);
canvas.drawArc(oval, -90, 360, false, mPaint);
}
}
布局文件:
android:layout_width="200dp"
android:layout_height="200dp"
android:background="#000000"
android:orientation="horizontal">
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/dly"
app:radius="200">
android:layout_marginLeft="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/dly"
app:radius="200">
***效果:
自定义一个viewgroup
这个其实也就是稍微复杂了一点,但是还是有迹可循的,只是稍微需要一点额外的耐心。
自定义一个viewgroup 需要注意的点如下:
一定是先重写onMeasure确定子view的宽高和自己的宽高以后 才可以继续写onlayout 对这些子view进行布局噢~~
viewgroup 的onMeasure其实就是遍历自己的view 对自己的每一个子view进行measure,绝大多数时候对子view的 measure都可以直接用 measureChild()这个方法来替代,简化我们的写法,如果你的viewgroup很复杂的话无法就是自己写一遍measureChild 而不是调用measureChild 罢了。
计算出viewgroup自己的尺寸并且保存,保存的方法还是哪个setMeasuredDimension 不要忘记了
逼不得已要重写measureChild方法的时候,其实也不难无非就是对父view的测量和子view的测量 做一个取舍关系而已,你看懂了基础的measureChild方法,以后就肯定会写自己的复杂的measureChild方法了。
下面是一个极简的例子,一个很简单的flowlayout的实现,没有对margin paddding做处理,也假设了每一个tag的高度是固定的,可以说是极为简单了,但是麻雀虽小 五脏俱全,足够你们好好理解自定义viewgroup的关键点了。
/**
* 写一个简单的flowlayout 从左到右的简单layout,如果宽度不够放 就直接另起一行layout
* 这个类似的开源控件有很多,有很多写的出色的,我这里只仅仅实现一个初级的flowlayout
* 也是最简单的,目的是为了理解自定义viewgroup的关键核心点。
*
* 比方说这里并没有对padding或者margin做特殊处理,你们自己写viewgroup的时候 记得把这些属性的处理都加上
* 否则一旦有人用了这些属性 发现没有生效就比较难看了。。。。。。
*/
publicclass SimpleFlowLayout extends ViewGroup {
publicSimpleFlowLayout(Context context) {
super(context);
}
publicSimpleFlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
publicSimpleFlowLayout(Context context, AttributeSet attrs,intdefStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* layout的算法 其实就是 不够放剩下一行 那另外放一行 这个过程一定要自己写一遍才能体会,
* 个人有个人的写法,说不定你的写法比开源的项目还要好
* 其实也没什么夸张的,无法就是前面onMeasure结束以后 你可以拿到所有子view和自己的 测量宽高 然后就算呗
*
* @param changed
* @param l
* @param t
* @param r
* @param b
*/
@Override
protected void onLayout(boolean changed, intl,intt,intr,intb) {
intchildTop = 0;
intchildLeft = 0;
intchildRight = 0;
intchildBottom = 0;
//已使用 width
intusedWidth = 0;
//customlayout 自己可使用的宽度
intlayoutWidth = getMeasuredWidth();
Log.v("wuyue","layoutWidth=="+ layoutWidth);
for(inti = 0; i
ViewchildView = getChildAt(i);
//取得这个子view要求的宽度和高度
intchildWidth = childView.getMeasuredWidth();
intchildHeight = childView.getMeasuredHeight();
//如果宽度不够了 就另外启动一行
if (layoutWidth - usedWidth
childLeft = 0;
usedWidth = 0;
childTop += childHeight;
childRight = childWidth;
childBottom = childTop + childHeight;
childView.layout(0, childTop, childRight, childBottom);
usedWidth = usedWidth + childWidth;
childLeft = childWidth;
continue;
}
childRight = childLeft + childWidth;
childBottom = childTop + childHeight;
childView.layout(childLeft, childTop, childRight, childBottom);
childLeft = childLeft + childWidth;
usedWidth = usedWidth + childWidth;
}
}
@Override
protected void onMeasure(intwidthMeasureSpec,intheightMeasureSpec) {
//先取出SimpleFlowLayout的父view对SimpleFlowLayout 的测量限制 这一步很重要噢。
//你只有知道自己的宽高 才能限制你子view的宽高
intwidthMode = MeasureSpec.getMode(widthMeasureSpec);
intheightMode = MeasureSpec.getMode(heightMeasureSpec);
intwidthSize = MeasureSpec.getSize(widthMeasureSpec);
intheightSize = MeasureSpec.getSize(heightMeasureSpec);
intusedWidth = 0; //已使用的宽度
intremaining = 0; //剩余可用宽度
inttotalHeight = 0; //总高度
intlineHeight = 0; //当前行高
for(inti = 0; i
ViewchildView = getChildAt(i);
LayoutParams lp = childView.getLayoutParams();
//先测量子view
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
//然后计算一下宽度里面 还有多少是可用的 也就是剩余可用宽度
remaining = widthSize - usedWidth;
//如果一行不够放了,也就是说这个子view测量的宽度 大于 这一行 剩下的宽度的时候 我们就要另外启一行了
if (childView.getMeasuredWidth() > remaining) {
//另外启动一行的时候,使用过的宽度 当然要设置为0
usedWidth = 0;
//另外启动一行了 我们的总高度也要加一下,不然高度就不对了
totalHeight = totalHeight + lineHeight;
}
//已使用 width 进行 累加
usedWidth = usedWidth + childView.getMeasuredWidth();
//当前 view的高度
lineHeight = childView.getMeasuredHeight();
}
//如果SimpleFlowLayout 的高度 为wrap cotent的时候 才用我们叠加的高度,否则,我们当然用父view对如果SimpleFlowLayout 限制的高度
if (heightMode == MeasureSpec.AT_MOST) {
heightSize = totalHeight;
}
setMeasuredDimension(widthSize, heightSize);
}
}
***看下效果
【编辑推荐】
【责任编辑:未丽燕 TEL:(010)68476606】
点赞 0
android 自定义viewgroup onmeasure,一篇文章搞懂Android 自定义Viewgroup的难点相关推荐
- Android NDK开发之旅(2):一篇文章搞定Android Studio中使用CMake进行NDK/JNI开发
Android NDK开发之旅(2):一篇文章搞定android Studio中使用CMake进行NDK/JNI开发 (码字不易,转载请声明出处:http://blog.csdn.NET/andrex ...
- c++ 计算正弦的近似值_一篇文章搞懂正弦保真性
本文介绍数字信号处理中"正弦保真性"这一概念,想要更好地理解本文所述内容,建议读者先阅读<一篇文章搞懂卷积>. 正弦保真性定义 一个正弦信号作为线性时不变系统的输入时, ...
- 一篇文章搞懂filebeat(ELK)
一篇文章搞懂filebeat(ELK) https://www.cnblogs.com/zsql/p/13137833.html 目录 一.filebeat是什么 1.1.filebeat和beats ...
- 一篇文章搞懂架构师的核心技能
" 这是架构师系列的第一篇:核心技能,希望这个系列能完全揭示架构师这个职位:我先从核心技能开始,后续还有架构师之路,架构实战等架构师系列文章. 本文作者 陈睿 优知学院创始人,前携程定制旅游 ...
- 一篇文章搞懂算法基础
源码地址 https://github.com/javanan/DataStructure 目录 时间复杂度介绍 空间复杂度介绍 递归算法与非递归算法区别和转换 折半查找/二分查找算法 链表实现 反转 ...
- 组件化开发实战_一篇文章搞懂什么是前端“组件化”开发
学过网页的朋友都知道,制作一个网页离不开HTML.CSS和JavaScript技术.对于初学者来来说,掌握这3门技术就已经很不容易了,为什么前端为什么又要搞出来一个"组件化"开发的 ...
- reactrouter监听路由变化_一篇文章搞懂前端路由原理解析和实现方式
在单页应用如此流行的今天,曾经令人惊叹的前端路由已经成为各大框架的基础标配,每个框架都提供了强大的路由功能,导致路由实现变的复杂. 想要搞懂路由内部实现还是有些困难的,但是如果只想了解路由实现基本原理 ...
- 【一篇文章搞懂】什么是分布式锁?为什么要用分布式锁?看这篇文章准没错!
简介 HikariCP 是用于创建和管理连接,利用"池"的方式复用连接减少资源开销,和其他数据源一样,也具有连接数控制.连接可靠性测试.连接泄露控制.缓存语句等功能,另外,和 dr ...
- 一篇文章搞懂STL中的空间配置器allocator(原创,多图,易懂)
Table of Contents 0.引入 1.标准的空间配置器allocator 2.更为高效的空间配置器alloc 2.1----对象的构造与析构 2.1.1 对象的构造:::construct ...
最新文章
- Java Day01-1
- C语言-运算符优先级及注意事项
- jxls使用excel公司_使用jXLS将Excel文件解析为JavaBeans
- html5 cs js字母验证码,JavaScript生成图形验证码
- python由编译器将源程序转化为机器语言_python初识
- 指针变量的声明、地址相关运算--“*”和“”
- Bailian3713 外星人翻译用数字转换模块【递归+映射】
- python和nodejs哪个写爬虫好_PythonNodejs 哪个比较适合写爬虫
- 【ACL2019】轻松了解张岳实验室的六篇paper
- 应用多元统计分析高惠璇pdf_EViews统计分析与应用pdf txt mobi下载及读书笔记
- c51单片机小车代码解释
- Android App开发常用尺寸规范
- UnwindSegue
- 计算机网络白龙飞,成电等你来 | 你的辅导员已上线,男神辅导员闪亮登场(一)...
- html自动幻灯片代码,简单常用的幻灯片播放实现代码
- 论如何设计博客分类标签系统
- error: RPC failed; curl 56 GnuTLS recv error (-9): A TLS packet with unexpected length was
- 专家警告全球芯片短缺可能持续到 2022 年之后
- 【UE4】物理材质(蓝图)
- python解包wxapkg_小程序反编译之获取wxapkg包
热门文章
- UDDI :一种 XML Web 服务
- 开机动画适配方案_2020 年 4 月前 App 启动画面、屏幕调整需按要求适配,否则存拒审风险!...
- 国外学python的软件_全球开发者调查报告:IT人最想学习 Go 和 Python、美国开发者收入最高...
- 每日程序C语言27-矩阵对角线求和
- Java黑皮书课后题第10章:*10.1(Time类)设计一个名为Time的类。编写一个测试程序,创建两个Time对象(使用new Time()和new Time(555550000))
- Java黑皮书课后题第8章:*8.3(按考分对学生排序)重写程序清单8-2,按照正确答案个数的升序显示学生
- C语言学习之用指针变量,将数组a中n个整数按相反顺序存放
- 【[TJOI2018]异或】
- easy-ui表单校验---针对单个字段,多重校验(有参数校验+无参数检验)
- [SDOI2013]直径 (树的直径,贪心)