前言

一直以来 写一些小demo没什么总结性,今天先放个坑,谈谈对自定义的理解 也作为自己的网络笔记。
讲讲自己对常用方法的理解,后续会补上相关实际的实例
先分享一些我已经发过的比较简单的自定义view

简易实现Listview滑动删除 (通用任意view)
Android DIY之路 (一) 指定区域多图片合成 放大 缩小 镜像 旋转 等
Android DIY之路 (三) 手绘 仅在限定区域留下痕迹 并再现这一过程
自定义Toast效果,windows层添加view,多个Toast效果
自定义edittext 手机格式 银行卡格式
手势密码实用demo
Android DIY之路 (四)拖拽替换,一个view发送其他所有view绑定即可监听到。拖拽排序的核心
都是日常的积累,大家可以讨论,比如再现手绘过程,都还没写完善。

进入正题

自定义view核心无非3点

测量==== 布局==== 绘制

*自定义事件

那么根据这个思路,我们先直接继承一个View啥也不写,打印出可能会用到的方法,扔在Activity里面进行研究。

public class ExView extends View {private String exViewMethodInturn = "exViewMethodInturn----->";public ExView(Context context) {this(context, null);}public ExView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public ExView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}public String getExViewMethodInturn() {return exViewMethodInturn;}private void init() {exViewMethodInturn += "init-->";Log.i("rex", "init");}//重写常用的方法,进行打印
}
<com.justforview.view.ExViewandroid:id="@+id/exView"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="10dp" android:background="#DEDEDE"
/>

调用顺序:

init(构造方法)
—–>onFinishInflate
—–>onAttachedToWindow
—–>onMeasure
—–>onSizeChanged
—–>onLayout
—–>onMeasure( -2147482978,-2147482656)
—–>onLayout
—–>onDraw
—–>dispatchDraw

(经过打印,和手机上画面显示,我们发现有些方法被调用了多次,onMeasure 数值什么鬼?,wrap无效的现象。下面我们来一一探讨)

*废话*:官方的方法名,一般起的 都能理解个一二,以后回忆起来也好记,measure测量 哪里绘制,draw诸如此类。(学好英语很重要)

构造方法

一般用作(AttributeSet attrs),自定义属性的获取,和一些初始化, new Paint() 啊 TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.XXXiew); a.getColor啥的; 暂不讨论。

1.onFinishInflate

Inflate 很面熟吧 我就记“打气”比如给娃娃充气,使她感到充实,成为一个完整的个体。用一个XML源填充view,加上finish,那就是完事。
常用:LayoutInflater.from(getContext()).inflate() / View.inflate()
所以finish了把xml打气成一起view,但要注意的是,每个部位都充气充好,才是真正的完事。
即当View中所有的子控件均被映射成xml后触发

2.onAttachedToWindow

Attach(附在)Window(窗体)上
View和Window缠绵在一起(绑定时)就会调用这个函数(那么这个函数的作用 你懂得)

按再打印出生命周期onCreate->onStart->onResume->onAttachedToWindow

Activity的onResume生命周期后就能获取DecorView的LayoutParam,进而可以设置高度和宽度了。根据上面贴出的生命周期图,onResume()后面是onAttachedToWindow(),并且onAttachedToWindow只会调用一次,不会受用户操作行为影响。所以
在onAttachedToWindow中进行窗口尺寸的修改再合适不过了
DecorView的LayoutParams是在ActivityThread的handleResumeActivity中设置的,并且该函数会调用Activity的onResume生命周期,所以在onResume之后可以设置窗体尺寸

3.onMeasure

测量相信多多少会提到,比如获取宽高的时候为0啊。测量也非常重要,那它是干嘛的,怎么测量,自定义的时候有什么作用。包括上述例子中经典现象wrap_content是无效的(填具体数值有效)。widthMeasureSpec, heightMeasureSpec -2147482978又是什么。咋还被多次调用呢?这方法被重写的几率高吗?其实不高。大多需求影响没有那么大(那说个卵…)

好的,那就开始说个卵,你不重写它,wrap_content是无效的,简言之,你不告诉它包裹的内容是啥,它就默认干别的去了比如漫不经心的充满布局啥的。TextView 告诉了它包裹的是字,在哪里告诉的 就是onMeasure。可能到这里好像卵用性还不够。

好了,正经的说View在屏幕上显示出来要先经过measure(计算)宽高 和 layout(布局)位置
3.1 onMeasure/setMeasuredDimension/specSize

为方便研究,我们直接在将宽高设置成,200,100px(不设置dp是为了更明显看他的作用,因为数据实际传递是生成的px,as里面可以设置px)
我们先看widthMeasureSpec(heightMeasureSpec类似)是什么

大小+类型
(多余的解释)
一个MeasureSpec封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求。一个MeasureSpec由大小和模式组成。
MeasureSpec是通过将一个int(32)的数组成而成的,本质是一个int数,MeasureSpec由两部分组成:SepcMode 和 SpecSize 。其中SpecMode为MeasueSpec的高2位,SpecSize为MeasureSpec的低30位。

{
//经查阅方法转化
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
<----->MeasureSpec.makeMeasureSpec//得出来的恰好是200 specMode 先不变。
//说明通过layout-width=200经过上面两个方法之后 到了view的onMeasure的经过MeasureSpec形成边界参数widthMeasureSpec传进来//那么我们除以2 再给super.onmesure试试 int newwidthMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec) / 2, MeasureSpec.getMode(widthMeasureSpec));super.onMeasure(newwidthMeasureSpec, heightMeasureSpec);}
//而实际宽度是  super.onMeasure(newwidthMeasureSpec, heightMeasureSpec)中的newwidthMeasureSpec

到这里就解释了一个疑惑xml中layout-width为什么不是width,一如layout-gravity和gravity关系?(当然这只是我的推测)layout-width是告诉父布局,我想要多大的宽高,父布局surper再给你实际大小。貌似很有道理,我们还是看看源码莫意淫。

源码就一个setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
这个估计就是直接告诉爹地我要多大地盘。我们一试。根据他这个getDefaultSize推断这里是实际宽度,而不是一长串什么鬼(控件可获得的空间以及关于这个空间描述的元数据)。
直接 setMeasuredDimension(100,100);注释掉super依然是个100x100正方形.其实不注释也没关系。实际就是两次的setMeasuredDimension取最后一个效果。
那么ok 从layout-width到widthMeasureSpec最后到再到实际效果的setMeasuredDimension。形成最终的width
网上的一个解释是父布局给你一个限制,你可以遵守或者不遵守。
我觉得是当子view还在xml里面的时候告诉了父亲自己想要多大的空间,等到终于有一天,自己被inflate成为view的时候,父亲将这个值还给了子view,子view可以选择遵从父亲留存当时最初的梦想,也可以变心自己去任性setMeasuredDimension。
上段都是废话。核心就是能得到xml里面(或者inflate之前)的值,你可以选择改或者不改(setMeasuredDimension/super.measuer—->setMeasuredDimension)。

如果你不改就会取默认的。


protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {int result = size;int specMode = MeasureSpec.getMode(measureSpec);int specSize = MeasureSpec.getSize(measureSpec);switch (specMode) {case MeasureSpec.UNSPECIFIED:result = size;break;case MeasureSpec.AT_MOST:case MeasureSpec.EXACTLY:result = specSize;break;}return result;
}

(默认填充父布局?)现在大小我们知道了,再看看最后一个specMode 是什么。

3.2 specMode

specMode 根据数值(上面的方法也可以看到):
1. EXACTLY
表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
2. AT_MOST
表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
3. UNSPECIFIED
表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。
3.3 wrap_content无效的原因

由上面两个方法得出默认是没有处理wrap_content,且默认效果是填充父容器

(详细原因)
在默认实现中,AT_MOST和EXACTLY两种模式都会被设置成specSize。我们也知道AT_MOST对应于wrap_content,而EXACTLY对应于match_parent和具体数值情况。也就说默认情况下wrap_content和match_parent是具有相同的效果的。那有人会问:wrap_content和match_parent具有相同的效果,为什么是填充父容器的效果呢? 下面讲一下View的绘制过程: View的绘制首先起于ViewRootImpl,并且View的三个流程也是通过ViewRootImpl来完成的,在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建viewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联。之后,View的绘制过程从ViewRootImpl的performTraversals方法开始,它经过measure、layout、draw三个过程最终将一个View绘制出来,由于DecorView是Android的一个页面的顶级View,所以绘制过程首先会从DecorView开始,又因为DecorView是一个ViewGroup,它会遍历绘制所有的子View,我们注意到在protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法中会有两个参数,其实这两个参数是在ViewGroup中传入进去的,最初是在DecorView中赋值的,且其值就是屏幕的宽度和高度。.也就是下面的windowSize

private int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
关于wrap_content无效的可参考博客
https://my.oschina.net/ccqy66/blog/616662
3.4很多次的onmeasure
一个界面的展示可能会涉及到很多次的measure,因为一个布局中一般都会包含多个子视图,每个视图都需要经历一次measure过程。ViewGroup中定义了一个measureChildren()方法来去测量子视图的大小

4.onSizeChanged

它的方法名已经告诉我们了,这个方法会在这个view的大小发生改变是被系统调用,我们要记住的就是view大小变化,这个方法就被执行就可以了.我们啥也没变它也调用了?0开始变的嘛。参数名int w, int h, int oldw, int oldh。就不再赘述。

5.onLayout

该方法是View的放置方法,在View类实现。调用该方法需要传入放置View的矩形空间左上角left、top值和右下角right、bottom值。这四个值是相对于父控件而言的。例如传入的是(10, 10, 100, 100),则该View在距离父控件的左上角位置(10, 10)处显示,显示的大小是宽高是90(参数r,b是相对左上角的),这有点像绝对布局。
平常开发所用到RelativeLayout、LinearLayout、FrameLayout…这些都是继承ViewGroup的布局。这些布局的实现都是通过都实现ViewGroup的onLayout方法,只是实现方法不一样而已

  // 动态获取子View实例for (int i = 0, size = getChildCount(); i < size; i++) {View view = getChildAt(i);// 放置子View,宽高都是100view.layout(l, t, l + 100, t + 100);l += 100 + padding;}

onMeasure在整个界面上需要放置一样东西或拿掉一样东西时会调用。比如addView就是放置,removeview就是拿掉,另外比较特殊的是,child设置为gone会触发onMeasure,但是invisible不会触发onMeasure。一旦执行过onMeasure,往往就会执行onLayout来重新布局

5.onDraw/dispatchDraw ()

由于onMeasure大佬太占位置,写了半天才写到能体现自定义view的精髓的Draw.这也是自定义view最有成就的地方
调用流程 :
mView.draw()开始绘制,draw()方法实现的功能如下:
1 、绘制该View的背景
2 、为显示渐变框做一些准备操作(见5,大多数情况下,不需要改渐变框)
3、调用onDraw()方法绘制视图本身 (每个View都需要重载该方法,ViewGroup不需要实现该方法)
4、调用dispatchDraw ()方法绘制子视图(如果该View类型不为ViewGroup,即不包含子视图,不需要重载该方法)
值得说明的是,ViewGroup类已经为我们重写了dispatchDraw ()的功能实现,应用程序一般不需要重写该方法,但可以重载父类
函数实现具体的功能。

dispatchDraw()方法内部会遍历每个子视图,调用drawChild()去重新回调每个子视图的draw()方法(注意,这个
地方“需要重绘”的视图才会调用draw()方法)。值得说明的是,ViewGroup类已经为我们重写了dispatchDraw()的功能
实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。

但我曾经写到高亮引导(或者二维码中间扣一块透明里面有用到dispatchDraw )
自定义高亮区域 作用户引导的思路

    @Overrideprotected void dispatchDraw(Canvas canvas) {//核心先后顺序if (rects.size() != 0) {drawRect(canvas);//绘制高亮区域super.dispatchDraw(canvas);//绘制子view -->如提示文字 箭头}}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);//绘制半透明背景}

*invalidate()方法

说明:请求重绘View树,即draw()过程,假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些“需要重绘的”
视图,即谁(View的话,只绘制该View ;ViewGroup,则绘制整个ViewGroup)请求invalidate()方法,就绘制该视图。

 一般引起invalidate()操作的函数如下:1、直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身。2、setSelection()方法 :请求重新draw(),但只会绘制调用者本身。3、setVisibility()方法 : 当View可视状态在INVISIBLE转换VISIBLE时,会间接调用invalidate()方法,继而绘制该View。4 、setEnabled()方法 : 请求重新draw(),但不会重新绘制任何视图包括该调用者本身。

*requestLayout()方法

会导致调用measure()过程 和 layout()过程 。
说明:只是对View树重新布局layout过程包括measure()和layout()过程,不会调用draw()过程,但不会重新绘制
任何视图包括该调用者本身。

一般引起invalidate()操作的函数如下:1、setVisibility()方法:当View的可视状态在INVISIBLE/ VISIBLE 转换为GONE状态时,会间接调用requestLayout() 和invalidate方法。
同时,由于整个个View树大小发生了变化,会请求measure()过程以及draw()过程,同样地,只绘制需要“重新绘制”的视图。requestFocus()函数说明:说明:请求View树的draw()过程,但只绘制“需要重绘”的视图。

下面一篇讲述关于绘制过程比较深刻的微博 虽年代久远,但依然获益匪浅。

http://blog.csdn.net/qinjuning/article/details/7110211/

持续更新…

当你准备自定义view的时候相关推荐

  1. Android自定义View基本步骤

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

  2. Android自定义View —— TypedArray

    在上一篇中Android 自定义View Canvas -- Bitmap写到了TypedArray 这个属性 下面也简单的说一下TypedArray的使用 TypedArray 的作用: 用于从该结 ...

  3. Android 自定义View Canvas —— Bitmap

    Bitmap 绘制图片 常用的方法有一下几种 (1) drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint ...

  4. Android 自定义View —— Canvas

    上一篇在android 自定义view Paint 里面 说了几种常见的Point 属性 绘制图形的时候下面总有一个canvas ,Canvas 是是画布 上面可以绘制点,线,正方形,圆,等等,需要和 ...

  5. Android 自定义View —— Paint

    上一篇说了自定义view的坐标系以及view 的使用,下面说下自定义view Paint 的使用 Paint 相对于画笔 ,可以使用Paint 来决定画的内容的颜色,边距粗细,设置样式,字体大小 ,等 ...

  6. Android 自定义View (入门 篇) 的使用

    每次都是过了很久都需要温习一下,自己打算整理一下方便查阅, 自定义view 首选需要明白的就是它的坐标系了,以手机左上角为起始点(0.0),横向的为x轴,竖向的为y轴 为了更好的理解我画了一幅草图如下 ...

  7. 28自定义View 模仿联系人字母侧栏

    自定义View LetterView.java package com.qf.sxy.customview02;import android.content.Context; import andro ...

  8. android炫酷的自定义view,Android自定义View实现炫酷进度条

    本文实例为大家分享了Android实现炫酷进度条的具体代码,供大家参考,具体内容如下 下面我们来实现如下效果: 第一步:创建attrs文件夹,自定义属性: 第二步:自定义View: /** * Cre ...

  9. android 自定义音乐圆形进度条,Android自定义View实现音频播放圆形进度条

    本篇文章介绍自定义View配合属性动画来实现如下的效果 实现思路如下: 根据播放按钮的图片大小计算出圆形进度条的大小 根据音频的时间长度计算出圆形进度条绘制的弧度 通过Handler刷新界面来更新圆形 ...

  10. Android使用自定义View时:Error inflating class错误的原因。

    当在布局文件里使用自定义的View的时候,出现Error inflating class错误的原因: 1.没有定义inflate需要的默认构造函数: eg:自定义View为TestView,需要定义T ...

最新文章

  1. 主瓣、栅瓣和旁瓣的定义
  2. 怎样学好python-怎样学好python
  3. SQL优化常用方法10
  4. git for windows的下载地址
  5. 并发编程-concurrent指南-线程池ExecutorService的使用
  6. ICCV 2019 | 北邮提出高阶注意力模型,大幅改进行人重识别SOTA精度
  7. 【极乐净土mmd】动作+镜头数据下载
  8. 免费好用的英语词频统计软件(下载地址在文末)
  9. TPS2552DBVR配电开关
  10. Java StackTraceElement源码总结 StackTraceElement源码注释翻译和解析中英文对照版
  11. 大白菜装机教程win10_大白菜U盘启动工具|大白菜超级U盘启动制作工具 V6.0_2009.25官方版下载...
  12. java meta-inf作用_java - META-INF的目的是什么?
  13. 健康小贴士:喝酒时别点哪些菜_新闻中心_新浪网
  14. Flutter | Sliver 系列
  15. 如何对List去重,含Java8写法
  16. Class6 基于ECS和NAS搭建个人网盘
  17. anbox 使用情况_Anbox让您在Linux桌面上运行Android应用程序
  18. jcharArray转化为char [ ]
  19. php的redis函数
  20. ( 285 => 347)JQ的继承方法

热门文章

  1. 生活不可能像你想象的那么好,但也不会像你想象的那么糟。 ——莫泊桑《羊脂球》
  2. 《穿普拉达的女王》-观后感
  3. 中国最感人的8首爱情诗
  4. Extending Air
  5. 使用ADB 查看模拟器得日志,unity得日志 以及保存
  6. JAVA经典算法40题
  7. IM系统数据库设计 前端逻辑处理
  8. [语录]足球解说员贺炜语录
  9. Auto.js Pro 调起APP并跳转至指定页面
  10. 人类一败涂地做图教程_人类一败涂地皮肤怎么弄 人类一败涂地皮肤制作教程...