一、自定义View的基本步骤

本篇文章的核心知识点,并不是自定义View的全部讲解,主要是通过一个简单的demo了解自定义View的MeasureSpecMode模式。

自定义View基本分为自定义View和自定义ViewGroup。

自定义View的步骤:

  1. 继承View重写构造方法(有四个构造方法,不同的使用场景可以了解下)
  2. 自定义属性,在构造方法中初始化属性
  3. 重写onMeasure方法测量宽高
  4. 重写onDraw方法绘制控件

关于View的绘制流程中,三个核心的回调方法onMeasure、onLayout、onDraw,在自定义View的时候,onLayout方法基本不用,onLayout方法是在ViewGroup自定的时候需要重写的方法,这个也比较好解释,就不多做介绍。

下面看一个简单的自定义TextView的代码编写:

CTextView1.java

public class CTextView1 extends View {private  Paint mPaint;//计算字体所需要的范围private  Rect mTextBounds;private int color;private String text;//一般纯代码创建View的时候,可以使用这个构造函数public CTextView1(Context context) {this(context,null);}//加载布局文件的时候会调用public CTextView1(Context context, @Nullable AttributeSet attrs) {this(context, attrs,0);}public CTextView1(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, 0);//自定义属性TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.MNView);color = typedArray.getInteger(R.styleable.MNView_mn_color,R.color.colorAccent);text = typedArray.getString(R.styleable.MNView_mn_text);typedArray.recycle();//初始化画笔mPaint= new Paint();mTextBounds = new Rect();mPaint.setColor(color);mPaint.setTextSize(50);//确定paint的绘制范围mPaint.getTextBounds(text,0,text.length(),mTextBounds);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);//这个函数简单理解下:文字实际内容的绘制源点;//text:文本内容,x、y是源点坐标,x:绘制文本的起始x坐标;y:实际上是展示文本的基线上起点的y坐标canvas.drawText(text,0,getPaddingTop()+mTextBounds.height(),mPaint);}
}

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#AB7CEF"tools:context=".MainActivity"><com.hym.view.CTextView1android:layout_width="wrap_content"android:layout_height="wrap_content"app:mn_text="自定义View"android:padding="10dp"android:background="@color/colorAccent"/></LinearLayout>

运行看一下效果:

这里是截图,运行出来的效果也是一致的。这里面很显然wrap_content是失效的。那么这里面为什么wrap_content会失效呢,就是接下来我们今天核心要回顾和学习的知识点。

二、View的MeasureSpec模式

View的MeasureSpec是由父容器的MeasureSpec以及自身的layoutParams决定的。

parentSpecMode\childLayoutParams Exactly AT_MOST UNSPECIFIED
固定值:dp/px Exactly:固定大小,即子View实际定义大小 Exactly:固定大小,即子View实际定义大小 Exactly:固定大小,即子View实际定义大小
match_parent Exactly:父View的大小 AT_MOST:父View的大小 UNSPECIFIED:0
wrap_content AT_MOST:父View的大小 AT_MOST:父View的大小 UNSPECIFIED:0

1:父容器为Exactly模式:(设定了具体数值宽高)

如果子View是Exactly模式(当用户指定了具体的数值(宽/高)),那么这个子View的resultSize就是你赋值的具体值。

如果子View指定的是match_parent,那么子View的resultSize依然是父容器给予的最大值,模式还是Exactly。

如果子View指定的是wrap_content,那么子View的resultSize也是父容器给予的最大值,但是模式变成了At_most。(这也是经常被提问到在自定义View的过程中,wrap_content失效的问题)

2:父容器为At_most模式:(对应父容器设置了wrap_content)

如果子View是Exactly模式(当用户指定了具体的数值(宽/高)),那么子View的resultSize就是具体制定的大小。

如果子View指定的是match_parent,那么子View的resultSize是父容器给予的最大值,模式还是At_most。

如果子View指定的wrap_content,子view的resultSize就是父控件的size,模式是at_most。

3:父容器为Unspecified模式:(这个模式基本用不到,大部分都是在系统内部使用)

如果子View是Exactly模式(当用户指定了具体的数值(宽/高)),那么子View的resultSize就是赋值的具体的值。

如果子View指定的match_parent,子View的大小为0,模式也为Unspecified。

如果子View指定的wrap_content,子View的大小为0,模式为Unspecified。

Unspecified模式,就总结一句话,父View对子View不做限制,子View有大小,就是子View设置的大小,其它均为0,不多研究。

上面的内容,可能有些拗口,对1、2种情况再做一下解释,来帮助理解:

如何确定一个View的MeasureSpecMode?

layoutParams设置的三种情况+父View的MeasureSpecMode来确定一个View当前的MeasureSpecMode。

View为固定大小,父View无论为什么模式,均为Exactly模式;

View为match_parent,父View为固定大小,子View就能确定为固定大小(即父View的大小),故为Exactly模式;

View为match_parent,父View为非固定大小,子View的大小跟随父View(但也不确定具体大小),故为At_most模式;

View为wrap_content,父View是固定大小或是非固定大小(只要非UnSpecified模式),子View都是父亲View的大小,故为At_most;

从View的MeasureSpec的模式确定角度,归纳上述描述为如下表格

View的MeasureSpec 模式 条件
Exactly View为设置的固定宽高||View为match_parent,父View为确定宽高(Exactly模式)
At_most View为wrap_content||View为match_parent,父View为非确定宽高(Exactly模式)

通过上面的多方解释,应该算是到理解状态,具体MeasureSpec的精确确认过程,需要通过源码的解析,深度确认,但是上述结论是没有问题的。

通过上面的内容,我们也能看到,如果我们没有做任何特殊处理的情况,即使我的自定义View在布局中使用,使用了wrap_content,其实也不能达到我们所理解的当前View的高度即是子View的高度和,而是父View的大小。

于是,我们重新改造上面的onMeasure方法:

public class CTextView extends View {private  Paint mPaint;//计算字体所需要的范围private  Rect mTextBounds;private int color;private String text;private int mWidth;private int mHeight;//一般纯代码创建View的时候,可以使用这个构造函数public CTextView(Context context) {this(context,null);}//加载布局文件的时候会调用public CTextView(Context context, @Nullable AttributeSet attrs) {this(context, attrs,0);}public CTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, 0);//自定义属性TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.MNView);color = typedArray.getInteger(R.styleable.MNView_mn_color,R.color.colorAccent);text = typedArray.getString(R.styleable.MNView_mn_text);typedArray.recycle();//初始化画笔mPaint= new Paint();mTextBounds = new Rect();mPaint.setColor(color);mPaint.setTextSize(50);//确定paint的绘制范围mPaint.getTextBounds(text,0,text.length(),mTextBounds);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);//获取宽度的测量规范模式int specMode = MeasureSpec.getMode(widthMeasureSpec);int specWidth = MeasureSpec.getSize(widthMeasureSpec);Log.i("onMeasure 模式和宽度","specMode:"+specMode+"|specWidth:"+specWidth);if(specMode == MeasureSpec.EXACTLY){//当前view指定的match_parent或者指定固定值大小mWidth=specWidth;}else{//At_most 模式,当前View的layoutParams设置的是wrap_content//At_most 模式,子View的宽高是父View的宽高(无论父View是Exactly模式还是At_most模式)mWidth=getPaddingLeft()+mTextBounds.width()+getPaddingRight();}specMode = MeasureSpec.getMode(widthMeasureSpec);int specHeight = MeasureSpec.getSize(widthMeasureSpec);Log.i("onMeasure 模式和宽度","specMode:"+specMode+"|specHeight:"+specHeight);if(specMode == MeasureSpec.EXACTLY){//当前view指定的match_parent或者指定固定值大小mHeight=specHeight;}else{//At_most 模式,当前View的layoutParams设置的是wrap_content//At_most 模式,子View的宽高是父View的宽高(无论父View是Exactly模式还是At_most模式)mHeight=getPaddingTop()+mTextBounds.height()+getPaddingBottom();}//要不测量出来的宽高,进行设置Log.i("测量后的结果","mWidth:"+mWidth+"|mHeight:"+mHeight);setMeasuredDimension(mWidth,mHeight);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);//这个函数简单理解下:文字实际内容的绘制源点;//text:文本内容,x、y是源点坐标,x:绘制文本的起始x坐标;y:实际上是展示文本的基线上起点的y坐标canvas.drawText(text,0,getPaddingTop()+mTextBounds.height(),mPaint);}
}

再看下图,我们的效果就正常了。

关于自定义ViewGroup,针对于我们自己设计的ViewGroup的特性,正常情况下,我们重写onMeasure方法的时候,也是结合measureSpec测量模式,对我们的实际子View进行测量,同时要处理onLayout方法。
及时获得更多更新,关注gongzhonghao:Hym4Android

通过自定义View了解MeasureSpecMode相关推荐

  1. android 自定义取色器,【Android自定义View】仿Photoshop取色器ColorPicker(二)

    ColorPicker 一款仿Photoshop取色器的Android版取色器. 前言 上一篇已经简单介绍了ColorPicker的项目结构以及两种颜色空间,接下来我们详细解析一下ColorPicke ...

  2. Android自定义view之measure、layout、draw三大流程

    自定义view之measure.layout.draw三大流程 一个view要显示出来,需要经过测量.布局和绘制这三个过程,本章就这三个流程详细探讨一下.View的三大流程具体分析起来比较复杂,本文不 ...

  3. android自定义起止时间的时间刻度尺,Android 自定义View篇(六)实现时钟表盘效果...

    前言 Android 自定义 View 是高级进阶不可或缺的内容,日常工作中,经常会遇到产品.UI 设计出花里胡哨的界面.当系统自带的控件不能满足开发需求时,就只能自己动手撸一个效果. 本文就带自定义 ...

  4. 【朝花夕拾】Android自定义View篇之(一)View绘制流程

    前言 转载请申明转自[https://www.cnblogs.com/andy-songwei/p/10955062.html]谢谢! 自定义View.多线程.网络,被认为是Android开发者必须牢 ...

  5. 【朝花夕拾】Android自定义View之(一)手把手教你看懂View绘制流程——向源码要答案

    前言 原文:Android自定义View之(一)手把手教你看懂View绘制流程--向源码要答案 View作为整个app的颜值担当,在Android体系中占有重要的地位.深入理解Android View ...

  6. Android自定义View(二)

    文章目录 1.构造函数 2.onMeasure() 2.1.MeasureSpec 3.onSizeChanged() 4.onLayout() 5.onDraw() 上一篇: Android自定义V ...

  7. 自定义View 未完待续

    自定义View 文章目录 自定义View 什么是自定义View 自定义View和自定义ViewGroup的区别? 自定义View的步骤 布局 widthMeasureSpec和heightMeasur ...

  8. Android自定义view之View的测量过程全解析

    Android 应用层开发中绕不开自定义 View 这个话题,虽然现在 Github 上有形形色色的开源库供大家使用, 但是作为一名开发者而言,虽然不提倡重复造轮子,但是轮子都是造出来的.碰到一些新鲜 ...

  9. Android自定义View之(一)View绘制流程详解——向源码要答案

    前言 View作为整个app的颜值担当,在Android体系中占有重要的地位.深入理解Android View的绘制流程,对正确使用View来构建赏心悦目的外观,以及用自定义View来设计理想中的酷炫 ...

  10. Android自定义View基本步骤

    一.自定义属性 1.在res下的values下面新建attrs.xml 2.在布局中使用,声明命名空间 3.在自定义View构造方法中通过TypedArray获取属性 4.必须回收 array.rec ...

最新文章

  1. django redirect传递参数_Django中间件
  2. Nginx伪静态配置和常用Rewrite伪静态规则集锦
  3. git的简易上手笔记,及常用命令集合
  4. [转]Linux下用gcc/g++生成静态库和动态库(Z)
  5. 秒赞机器人好友_qq空间秒赞机器人好友 qq空间说说秒赞神器
  6. DTU网关连接MQTT服务器、MQTT.fx工具测试
  7. 滴滴在HBase性能与可用性上的探索与实践
  8. 干货 | 关于离岸银行开户(境外开户),看这一篇就够了
  9. 【实战】物联网安防监控项目【4】———从网页上控制A9的LED灯
  10. 在线客服系统源码软件代码+自动回复+可生成接入+手机版管理后台
  11. 华为运营商级路由器配置示例 | 静态BFD for IPv4静态路由
  12. 中国将购买43架A320和27架A330 总价百亿美元
  13. 网站是怎么了,一直http/1.1 302 found 浏览器假死了一样
  14. STM32F0系列FOC 源代码, 有单电阻采样和三电阻采样两种代码
  15. Fe3O4@mSiO2/DTX靶向载药纳米粒/FITC-SiO2@Fe3O4 荧光标记介孔二氧化硅磁颗粒
  16. openlayers6【七】地图控件controls详解
  17. 未来计算机将何去何从?
  18. centos根分区扩容,扩展磁盘
  19. 【目标检测】交通标识数据集CCTSDB标注处理(转YOLO)
  20. 大流量和高并发应对手段总结

热门文章

  1. 夸计算机老师的成语,赞美老师的成语句子
  2. MT7621路由器芯片/处理器参数介绍
  3. 数字信号处理知识点总结(一):卷积
  4. 惠普局域网共享打印机设置_打印机共享怎么设置 惠普打印机共享设置步骤介绍【详解】...
  5. 峰值信噪比和结构相似性
  6. windows 远程桌面连接(mstsc)删除下拉框的记录
  7. Android期末复习题库
  8. Linux编程中C语言头文件位置
  9. 微信小程序+Springboot实现宠物医院管理系统
  10. 2021-01-03