当你准备自定义view的时候
前言
一直以来 写一些小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的时候相关推荐
- Android自定义View基本步骤
一.自定义属性 1.在res下的values下面新建attrs.xml 2.在布局中使用,声明命名空间 3.在自定义View构造方法中通过TypedArray获取属性 4.必须回收 array.rec ...
- Android自定义View —— TypedArray
在上一篇中Android 自定义View Canvas -- Bitmap写到了TypedArray 这个属性 下面也简单的说一下TypedArray的使用 TypedArray 的作用: 用于从该结 ...
- Android 自定义View Canvas —— Bitmap
Bitmap 绘制图片 常用的方法有一下几种 (1) drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint ...
- Android 自定义View —— Canvas
上一篇在android 自定义view Paint 里面 说了几种常见的Point 属性 绘制图形的时候下面总有一个canvas ,Canvas 是是画布 上面可以绘制点,线,正方形,圆,等等,需要和 ...
- Android 自定义View —— Paint
上一篇说了自定义view的坐标系以及view 的使用,下面说下自定义view Paint 的使用 Paint 相对于画笔 ,可以使用Paint 来决定画的内容的颜色,边距粗细,设置样式,字体大小 ,等 ...
- Android 自定义View (入门 篇) 的使用
每次都是过了很久都需要温习一下,自己打算整理一下方便查阅, 自定义view 首选需要明白的就是它的坐标系了,以手机左上角为起始点(0.0),横向的为x轴,竖向的为y轴 为了更好的理解我画了一幅草图如下 ...
- 28自定义View 模仿联系人字母侧栏
自定义View LetterView.java package com.qf.sxy.customview02;import android.content.Context; import andro ...
- android炫酷的自定义view,Android自定义View实现炫酷进度条
本文实例为大家分享了Android实现炫酷进度条的具体代码,供大家参考,具体内容如下 下面我们来实现如下效果: 第一步:创建attrs文件夹,自定义属性: 第二步:自定义View: /** * Cre ...
- android 自定义音乐圆形进度条,Android自定义View实现音频播放圆形进度条
本篇文章介绍自定义View配合属性动画来实现如下的效果 实现思路如下: 根据播放按钮的图片大小计算出圆形进度条的大小 根据音频的时间长度计算出圆形进度条绘制的弧度 通过Handler刷新界面来更新圆形 ...
- Android使用自定义View时:Error inflating class错误的原因。
当在布局文件里使用自定义的View的时候,出现Error inflating class错误的原因: 1.没有定义inflate需要的默认构造函数: eg:自定义View为TestView,需要定义T ...
最新文章
- 主瓣、栅瓣和旁瓣的定义
- 怎样学好python-怎样学好python
- SQL优化常用方法10
- git for windows的下载地址
- 并发编程-concurrent指南-线程池ExecutorService的使用
- ICCV 2019 | 北邮提出高阶注意力模型,大幅改进行人重识别SOTA精度
- 【极乐净土mmd】动作+镜头数据下载
- 免费好用的英语词频统计软件(下载地址在文末)
- TPS2552DBVR配电开关
- Java StackTraceElement源码总结 StackTraceElement源码注释翻译和解析中英文对照版
- 大白菜装机教程win10_大白菜U盘启动工具|大白菜超级U盘启动制作工具 V6.0_2009.25官方版下载...
- java meta-inf作用_java - META-INF的目的是什么?
- 健康小贴士:喝酒时别点哪些菜_新闻中心_新浪网
- Flutter | Sliver 系列
- 如何对List去重,含Java8写法
- Class6 基于ECS和NAS搭建个人网盘
- anbox 使用情况_Anbox让您在Linux桌面上运行Android应用程序
- jcharArray转化为char [ ]
- php的redis函数
- ( 285 => 347)JQ的继承方法