热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

一篇文章弄懂Android自定义viewgroup的相关难点

这篇文章主要给大家介绍了关于如何通过一篇文章弄懂Android中自定义viewgroup的一些相关难点,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

本文的目的

目的在于教会大家到底如何自定义viewgroup,自定义布局和自定义测量到底如何写。很多网上随便搜搜的概念和流程图
这里不再过多描述了,建议大家看本文之前,先看看基本的自定义viewgroup流程,心中有个大概即可。本文注重于实践
viewgroup 的测量布局流程基本梳理

稍微回顾下,基本的viewgroup绘制和布局流程中的重点:

1.view 在onMeasure()方法中进行自我测量和保存,也就是说对于view(不是viewgroup噢)来说一定在onMeasure方法中计算出自己的尺寸并且保存下来

2.viewgroup实际上最终也是循环从上大小来调用子view的measure方法,注意子view的measure其实最终调用的是子view的onMeasure 方法。所以我们理解这个过程为:

viewgroup循环遍历调用所有子view的onmeasure方法,利用onmeasure方法计算出来的大小,来确定这些子view最终可以占用的大小和所处的布局的位置。

3.measure方法是一个final方法,可以理解为做测量工作准备工作的,既然是final方法所以我们无法重写它,不需要过多
关注他,因为measure最终要调用onmeasure ,这个onmeasure我们是可以重写的。要关注这个。layout和onlayout是一样的关系。

4.父view调用子view的layout方法的时候会把之前measure阶段确定的位置和大小都传递给子view。

5.对于自定义view/viewgroup来说 我们几乎只需要关注下面三种需求:

  • 对于已有的android自带的view,我们只需要重写他的onMeasure方法即可。修改一下这个尺寸即可完成需求。
  • 对于android系统没有的,属于我们自定义的view,比上面那个要复杂一点,要完全重写onMeasure方法。
  • 第三种最复杂,需要重写onmeasure和onlayout2个方法,来完成一个复杂viewgroup的测量和布局。

6.onMeasure方法的特殊说明:

7.如何理解父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的高度。很是方便

看下效果

最后看下代码,重要的部分都写在注释里了,不再过多讲了。

public class BannerImageView extends ImageView {

 //宽高比
 float ratio;

 public BannerImageView(Context context) {
 super(context);
 }

 public BannerImageView(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();
 }

 public BannerImageView(Context context, AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 //人家自己的测量还是要自己走一遍的,因为这个方法内部会调用setMeasuredDimension方法来保存测量结果了
 //只有保存了以后 我们才能取得这个测量结果 否则你下面是取不到的
 super.onMeasure(widthMeasureSpec, heightMeasureSpec);

 //取测量结果
 int mWidth = getMeasuredWidth();

 int mHeight = (int) (mWidth * ratio);

 //保存了以后,父view就可以拿到这个测量的宽高了。不保存是拿不到的噢。
 setMeasuredDimension(mWidth, mHeight);
 }
}

自定义view,完全自己写onMeasure方法

首先明确一个结论:

对于完全自定义的view,完全自己写的onMeasure方法来说,你保存的宽高必须要符合父view的限制,否则会发生bug,
保存父view对子view的限制的方法也很简单直接调用resolveSize方法即可。


所以对于完全自定义的view onMeasure方法也不难写了,

  • 先算自己想要的宽高,比如你画了个圆,那么宽高就肯定是半径的两倍大小, 要是圆下面还有字,
  • 那么高度肯定除了半径的两倍还要有字体的大小。对吧。很简单。这个纯看你自定义view是啥样的
  • 算完自己想要的宽高以后 直接拿resolveSize 方法处理一下 即可。
  • 最后setMeasuredDimension 保存。

范例:

public class LoadingView extends View {

 //圆形的半径
 int radius;

 //圆形外部矩形rect的起点
 int left = 10, top = 30;


 Paint mPaint = new Paint();

 public LoadingView(Context context) {
 super(context);
 }

 public LoadingView(Context context, AttributeSet attrs) {
 super(context, attrs);
 TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LoadingView);
 radius = typedArray.getInt(R.styleable.LoadingView_radius, 0);
 }

 public LoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 super.onMeasure(widthMeasureSpec, heightMeasureSpec);


 int width = left + radius * 2;
 int height = 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);
 }
}

布局文件:



  

  
 

最后效果:

自定义一个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的时候 记得把这些属性的处理都加上 * 否则一旦有人用了这些属性 发现没有生效就比较难看了。。。。。。 */ public class SimpleFlowLayout extends ViewGroup { public SimpleFlowLayout(Context context) { super(context); } public SimpleFlowLayout(Context context, AttributeSet attrs) { super(context, attrs); } public SimpleFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } /** * layout的算法 其实就是 不够放剩下一行 那另外放一行 这个过程一定要自己写一遍才能体会, * 个人有个人的写法,说不定你的写法比开源的项目还要好 * 其实也没什么夸张的,无法就是前面onMeasure结束以后 你可以拿到所有子view和自己的 测量宽高 然后就算呗 * * @param changed * @param l * @param t * @param r * @param b */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childTop = 0; int childLeft = 0; int childRight = 0; int childBottom = 0; //已使用 width int usedWidth = 0; //customlayout 自己可使用的宽度 int layoutWidth = getMeasuredWidth(); Log.v("wuyue", "layout + layoutWidth); for (int i = 0; i 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); } }

最后看下效果


总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。


推荐阅读
  • 深入理解OAuth认证机制
    本文介绍了OAuth认证协议的核心概念及其工作原理。OAuth是一种开放标准,旨在为第三方应用提供安全的用户资源访问授权,同时确保用户的账户信息(如用户名和密码)不会暴露给第三方。 ... [详细]
  • 深入解析Android自定义View面试题
    本文探讨了Android Launcher开发中自定义View的重要性,并通过一道经典的面试题,帮助开发者更好地理解自定义View的实现细节。文章不仅涵盖了基础知识,还提供了实际操作建议。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 非公版RTX 3080显卡的革新与亮点
    本文深入探讨了图形显卡的进化历程,重点介绍了非公版RTX 3080显卡的技术特点和创新设计。 ... [详细]
  • 2023 ARM嵌入式系统全国技术巡讲旨在分享ARM公司在半导体知识产权(IP)领域的最新进展。作为全球领先的IP提供商,ARM在嵌入式处理器市场占据主导地位,其产品广泛应用于90%以上的嵌入式设备中。此次巡讲将邀请来自ARM、飞思卡尔以及华清远见教育集团的行业专家,共同探讨当前嵌入式系统的前沿技术和应用。 ... [详细]
  • 本文详细探讨了KMP算法中next数组的构建及其应用,重点分析了未改良和改良后的next数组在字符串匹配中的作用。通过具体实例和代码实现,帮助读者更好地理解KMP算法的核心原理。 ... [详细]
  • Søren Kierkegaard famously stated that life can only be understood in retrospect but must be lived moving forward. This perspective delves into the intricate relationship between our lived experiences and our reflections on them. ... [详细]
  • 计算机网络复习:第五章 网络层控制平面
    本文探讨了网络层的控制平面,包括转发和路由选择的基本原理。转发在数据平面上实现,通过配置路由器中的转发表完成;而路由选择则在控制平面上进行,涉及路由器中路由表的配置与更新。此外,文章还介绍了ICMP协议、两种控制平面的实现方法、路由选择算法及其分类等内容。 ... [详细]
  • 本文将介绍如何使用 Go 语言编写和运行一个简单的“Hello, World!”程序。内容涵盖开发环境配置、代码结构解析及执行步骤。 ... [详细]
  • 线性Kalman滤波器在多自由度车辆悬架主动控制中的应用研究
    本文探讨了线性Kalman滤波器(LKF)在不同自由度(2、4、7)的车辆悬架系统中进行主动控制的应用。通过详细的仿真分析,展示了LKF在提升悬架性能方面的潜力,并总结了调参过程中的关键要点。 ... [详细]
  • 本文探讨了Hive中内部表和外部表的区别及其在HDFS上的路径映射,详细解释了两者的创建、加载及删除操作,并提供了查看表详细信息的方法。通过对比这两种表类型,帮助读者理解如何更好地管理和保护数据。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • C++实现经典排序算法
    本文详细介绍了七种经典的排序算法及其性能分析。每种算法的平均、最坏和最好情况的时间复杂度、辅助空间需求以及稳定性都被列出,帮助读者全面了解这些排序方法的特点。 ... [详细]
  • 本文介绍如何利用动态规划算法解决经典的0-1背包问题。通过具体实例和代码实现,详细解释了在给定容量的背包中选择若干物品以最大化总价值的过程。 ... [详细]
  • 本文详细探讨了Java中的24种设计模式及其应用,并介绍了七大面向对象设计原则。通过创建型、结构型和行为型模式的分类,帮助开发者更好地理解和应用这些模式,提升代码质量和可维护性。 ... [详细]
author-avatar
CK92_474
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有