1.自定义View前首先要了解一下View的方法,虽然有些不一定要实现。

分类 方法 描述
创建 Constructors

View中有两种类型的构造方法,一种是在代码中构建View,另一种是填充布局文件构建View,

第二种构造方法要解析并应用布局文件中定义的任何属性。

onFinishInflate() 在来自于XML的View和它所有的子节点填充之后被调用。
Layout onMeasure 调用该方法来确定view及它所有子节点需要的尺寸
onLayout 当view需要为它的所有子节点指定大小和布局时,此方法被调用
onSizeChanged 当这个view的大小发生变化时,此方法被调用
Drawing onDraw 当view渲染它的内容时被调用
事件处理 onKeyDown Called when a new key event occurs.
onKeyUp Called when a key up event occurs.
onTrackballEvent 当轨迹球动作事件发生时被调用
onTouchEvent Called when a touch screen motion event occurs.
Focus onFocusChanged Called when the view gains or loses focus.
onWindowFocusChanged Called when the window containing the view gains or loses focus.
Attaching onAttachedToWindow Called when the view is attached to a window.
onDetachedFromWindow Called when the view is detached from its window.
onWindowVisibilityChanged Called when the visibility of the window containing the view has changed.

注:除以上表格内的方法,还有一个比较重要的方法,就是View的刷新方法:

{①整个view刷新 invalidate();

②刷新一个矩形区域invalidate(Rect dirty);

③刷新一个特性DrawableinvalidateDrawable(Drawable drawable);

⑤执行invalidate类的方法将会设置view为无效,最终导致onDraw方法被重新调用。}

2.具体调用上述方法的过程,如下图:

注:

①measure过程

作用:为整个View树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth),每个View的控件的实际宽高都是由父视图和本身视图决定的。

流程:

ViewRoot根对象地属性mView(其类型一般为ViewGroup类型)调用measure()方法去计算View树的大小,回调View/ViewGroup对象的onMeasure()方法,该方法实现的功能如下:

a、设置本View视图的最终大小,该功能的实现通过调用setMeasuredDimension()方法去设置实际的高(对应属性: mMeasuredHeight)和宽(对应属性:mMeasureWidth) ;

b、如果该View对象是个ViewGroup类型,需要重写该onMeasure()方法,对其子视图进行遍历的measure()过程;

对每个子视图的measure()过程,是通过调用父类ViewGroup.java类里的measureChildWithMargins()方法去实现,该方法内部只是简单地调用了View对象的measure()方法。

(由于measureChildWithMargins()方法只是一个过渡层更简单的做法是直接调用View对象的measure()方法)。

c、整个measure调用流程就是个树形的递归过程

伪代码:

 //回调View视图里的onMeasure过程
private void onMeasure(int height , int width){
//设置该view的实际宽(mMeasuredWidth)高(mMeasuredHeight)
//1、该方法必须在onMeasure调用,否者报异常。
setMeasuredDimension(h , l) ; //2、如果该View是ViewGroup类型,则对它的每个子View进行measure()过程
int childCount = getChildCount() ; for(int i=0 ;i<childCount ;i++){
//2.1、获得每个子View对象引用
View child = getChildAt(i) ; //整个measure()过程就是个递归过程
//该方法只是一个过滤器,最后会调用measure()过程 ;或者 measureChild(child , h, i)方法都
measureChildWithMargins(child , h, i) ; //其实,对于我们自己写的应用来说,最好的办法是去掉框架里的该方法,直接调用view.measure(),如下:
//child.measure(h, l)
}
} //该方法具体实现在ViewGroup.java里 。
protected void measureChildWithMargins(View v, int height , int width){
v.measure(h,l)
}

②layout过程

作用:为将整个根据子视图的大小以及布局参数将View树放到合适的位置上。

流程:

a 、layout方法会设置该View视图位于父视图的坐标轴,即mLeft,mTop,mLeft,mBottom(调用setFrame()函数去实现)接下来回调onLayout()方法(如果该View是ViewGroup对象,需

要实现该方法,对每个子视图进行布局) ;

b、如果该View是个ViewGroup类型,需要遍历每个子视图chiildView,调用该子视图的layout()方法去设置它的坐标值。

代码:

源代码中的layout方法

  /**
* Assign a size and position to a view and all of its
* descendants
*
* <p>This is the second phase of the layout mechanism.
* (The first is measuring). In this phase, each parent calls
* layout on all of its children to position them.
* This is typically done using the child measurements
* that were stored in the measure pass().</p>
*
* <p>Derived classes should not override this method.
* Derived classes with children should override
* onLayout. In that method, they should
* call layout on each of their children.</p>
*
* @param l Left position, relative to parent
* @param t Top position, relative to parent
* @param r Right position, relative to parent
* @param b Bottom position, relative to parent
*/
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
}

更易理解的伪代码:

 // layout()过程  ViewRoot.java
// 发起layout()的"发号者"在ViewRoot.java里的performTraversals()方法, mView.layout() private void performTraversals(){ //... View mView ;
mView.layout(left,top,right,bottom) ; //....
} //回调View视图里的onLayout过程 ,该方法只由ViewGroup类型实现
private void onLayout(int left , int top , right , bottom){ //如果该View不是ViewGroup类型
//调用setFrame()方法设置该控件的在父视图上的坐标轴 setFrame(l ,t , r ,b) ; //-------------------------- //如果该View是ViewGroup类型,则对它的每个子View进行layout()过程
int childCount = getChildCount() ; for(int i=0 ;i<childCount ;i++){
//2.1、获得每个子View对象引用
View child = getChildAt(i) ;
//整个layout()过程就是个递归过程
child.layout(l, t, r, b) ;
}
}

③.draw()过程

作用:

由ViewRoot对象的performTraversals()方法调用draw()方法发起绘制该View树,值得注意的是每次发起绘图时,并不会重新绘制每个View树的视图,而只会重新绘制那些“需要重绘”的视

图,View类内部变量包含了一个标志位DRAWN,当该视图需要重绘时,就会为该View添加该标志位。

流程:

mView.draw()开始绘制,draw()方法实现的功能如下:

a、绘制该View的背景

b、为显示渐变框做一些准备操作(见5,大多数情况下,不需要改渐变框)

c、调用onDraw()方法绘制视图本身 (每个View都需要重载该方法,ViewGroup不需要实现该方法)

d、调用dispatchDraw ()方法绘制子视图(如果该View类型不为ViewGroup,即不包含子视图,不需要重载该方法)

dispatchDraw()方法内部会遍历每个子视图,调用drawChild()去重新回调每个子视图的draw()方法(注意,这个 地方“需要重绘”的视图才会调用draw()方法)。

值得说明的是,ViewGroup类已经为我们重写了dispatchDraw()的功能实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。

e、绘制滚动条

伪代码:

 // draw()过程     ViewRoot.java
// 发起draw()的"发号者"在ViewRoot.java里的performTraversals()方法, 该方法会继续调用draw()方法开始绘图
private void draw(){ //...
View mView ;
mView.draw(canvas) ; //....
} //回调View视图里的onLayout过程 ,该方法只由ViewGroup类型实现
private void draw(Canvas canvas){
//该方法会做如下事情
//1 、绘制该View的背景
//2、为绘制渐变框做一些准备操作
//3、调用onDraw()方法绘制视图本身
//4、调用dispatchDraw()方法绘制每个子视图,dispatchDraw()已经在Android框架中实现了,在ViewGroup方法中。
// 应用程序程序一般不需要重写该方法,但可以捕获该方法的发生,做一些特别的事情。
//5、绘制渐变框
} //ViewGroup.java中的dispatchDraw()方法,应用程序一般不需要重写该方法
@Override
protected void dispatchDraw(Canvas canvas) {
//
//其实现方法类似如下:
int childCount = getChildCount() ; for(int i=0 ;i<childCount ;i++){
View child = getChildAt(i) ;
//调用drawChild完成
drawChild(child,canvas) ;
}
}
//ViewGroup.java中的dispatchDraw()方法,应用程序一般不需要重写该方法
protected void drawChild(View child,Canvas canvas) {
// ....
//简单的回调View对象的draw()方法,递归就这么产生了。
child.draw(canvas) ; //.........
}

④以上①②③过程,最终会直接或间接调用到三个函数,分别为invalidate(),requsetLaytout()以及requestFocus() ,接着这三个函数最终会调用到ViewRoot中的schedulTraversale()方

法,该函数然后发起一个异步消息,消息处理中调用performTraverser()方法对整个View进行遍历。

invalidate()方法:

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

个ViewGroup)请求invalidate()方法,就绘制该视图。

一般引起invalidate()操作的函数如下:

a、直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身。

b、setSelection()方法:请求重新draw(),但只会绘制调用者本身。

c、setVisibility()方法: 当View可视状态在INVISIBLE转换VISIBLE时,会间接调用invalidate()方法,继而绘制该View。

d、setEnabled()方法: 请求重新draw(),但不会重新绘制任何视图包括该调用者本身。

requestLayout()方法:会导致调用measure()过程 和 layout()过程 。

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

一般引起invalidate()操作的函数如下:

a、setVisibility()方法:

当View的可视状态在INVISIBLE/ VISIBLE 转换为GONE状态时,会间接调用requestLayout() 和invalidate方法。

同时,由于整个个View树大小发生了变化,会请求measure()过程以及draw()过程,同样地,只绘制需要“重新绘制”的视图。

requestFocus()方法:

说明:请求View树的draw()过程,但只绘制“需要重绘”的视图。

注:本博客第2条内容摘抄自:Android中View绘制流程以及invalidate()等相关方法分析

3.关于measure()方法

①该方法里面传入的参数是widthMeasureSpec和heightMeasureSpec,可以通过这两个参数获取规定的宽和高以及模式。关于模式有三种:

a、UNSPECIFIED说明容器对组件本身的尺寸没有任何限制,组件可以根据自己的需要随意规划自己的尺寸。这种情况下,容器提供的尺寸没有任何意义了;

b、EXACTLY说明容器严格要求其组件的尺寸必须为给定尺寸,不能自己决定尺寸大小;

c、AT_MOST说明容器提供的尺寸是一个最小值,也就是说,组件可以随意决定自己的尺寸,只要不大于容器指定的尺寸即可。可以通过方法public static int makeMeasureSpec(int size,

int mode)进行定义模式。

②一些常用变量解释

 //控件的width与height
Log.d("My_TextView", "getWidth : " + getWidth());
Log.d("My_TextView", "getHeight : " + getHeight());
//画布的width与height
Log.d("My_TextView", "canvas.getWidth : " + canvas.getWidth());
Log.d("My_TextView", "canvas.getHeight : " + canvas.getWidth());
//控件指定width,height后,画布會是個正方形,邊長為控件width,height中較大的值 9 //控件離屏幕的上,下,左,右的距离
Log.d("My_TextView", "getTop : " + getTop());
Log.d("My_TextView", "getBottom : " + getBottom());
Log.d("My_TextView", "getLeft : " + getLeft());
Log.d("My_TextView", "getRight : " + getRight()); //控件左上角的坐标,getX = getLeft, getY = getTop
Log.d("My_TextView", "getX : " + getX());
Log.d("My_TextView", "getY : " + getY());

android 自定义view详解的更多相关文章

  1. Android 自定义 View 详解

    View 的绘制系列文章: Android View 绘制流程之 DecorView 与 ViewRootImpl Android View 的绘制流程之 Measure 过程详解 (一) Andro ...

  2. 深入了解View实现原理以及自定义View详解

    下面几篇文章对View的原理讲的非常详细. Android LayoutInflater原理分析,带你一步步深入了解View(一) Android视图绘制流程完全解析,带你一步步深入了解View(二) ...

  3. android开发之自定义View 详解 资料整理 小冰原创整理,原创作品。

    2019独角兽企业重金招聘Python工程师标准>>> /** * 作者:David Zheng on 2015/11/7 15:38 * * 网站:http://www.93sec ...

  4. 【朝花夕拾】Android自定义View篇之(四)自定义View的三种实现方式及自定义属性使用介绍

    前言 转载请声明,转自[https://www.cnblogs.com/andy-songwei/p/10979161.html],谢谢! 尽管Android系统提供了不少控件,但是有很多酷炫效果仍然 ...

  5. Android 自定义View合集

    自定义控件学习 https://github.com/GcsSloop/AndroidNote/tree/master/CustomView 小良自定义控件合集 https://github.com/ ...

  6. Android之canvas详解

    首先说一下canvas类: Class Overview The Canvas class holds the "draw" calls. To draw something, y ...

  7. 【转】Android Canvas绘图详解(图文)

    转自:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2012/1212/703.html Android Canvas绘图详解(图文) 泡 ...

  8. android自定义View之NotePad出鞘记

    现在我们的手机上基本都会有一个记事本,用起来倒也还算方便,记事本这种东东,如果我想要自己实现,该怎么做呢?今天我们就通过自定义View的方式来自定义一个记事本.OK,废话不多说,先来看看效果图. 整个 ...

  9. Android自定义View(RollWeekView-炫酷的星期日期选择控件)

    转载请标明出处: http://blog.csdn.net/xmxkf/article/details/53420889 本文出自:[openXu的博客] 目录: 1分析 2定义控件布局 3定义Cus ...

随机推荐

  1. 有关sass

    一.sass编译为css文件 编译的方法有很多 1.koala编译  请参考 http://www.w3cplus.com/blog/777.html http://koala-app.com/ind ...

  2. JQuery直接调用asp.net后台WebMethod方法

    利用JQuery的$.ajax()可以很方便的调用asp.net的后台方法.[WebMethod]   命名空间 1.无参数的方法调用, 注意:1.方法一定要静态方法,而且要有[WebMethod]的 ...

  3. 如何更改Magento的Base URL

    Magento的Base URL是用于访问商店页面的URL,您也可以为单独一个store view设置一个Base Url.在改这项值之前请确保您的域名已经指向了网站所在服务器的IP,DNS解析完成后 ...

  4. Selenium2学习-024-WebUI自动化实战实例-022-网站不同分辨率下页面样式展示兼容性问题解决方案 -- 设置浏览器显示区域大小(无人值守,节约测试成本的福音,BOSS 最爱)

    在 Web UI 自动化测试的过程中,通常会测试页面在不同分辨率下的显示效果,即在不同大小的显示器上全屏浏览器后的页面展示,此种测试方法需要购置不同大小的显示器,或者频繁的设置屏幕分辨率,不仅浪费了大 ...

  5. webApi中参数传递

    webApi中参数传递 一:无参数的get方法: 前端:    function GetNoParam() { //为了统一:我们都采用$.ajax({}) 方法; $.ajax({ url: '/a ...

  6. 30天,O2O速成攻略【8.29杭州站】

    活动概况 时间:2015年8月29日13:30-16:30 地点:123茶楼(杭州上城区青年路27号2楼) 主办:APICloud.UPYUN.一起火 网址:www.apicloud.com 费用:免 ...

  7. 读取、写入excel数据

    在实际项目中,不可避免的会操作excel表格.一直以来都是读取excel表格,可今天为了写入excel表格,可是煞费苦心,终于完成,记录下来以便后续使用. 1.读取excel表格的数据 读取excel ...

  8. 在 virtualbox 的 centos7 虚拟机中安装增强工具

    在 virtualbox 的 centos7 虚拟机中安装增强工具 centos7 刚刚安装完成时,直接安装 virtualbox 增强工具会出错,需要先把 gcc / kernel-devel / ...

  9. Unicode 编码概念

    Unicode 编码概念 Unicode 编码可能是我们日常开发中接触最多的字符编码方式之一,其它常见的中文编码方式还包括 GB2132-80 / GB13000 / GBK / GB18030 .在 ...

  10. shell && 和 || 的短路使用

    shell && 和 || 的短路使用 && 和 || 在 shell 中分别表示 and 和 or,和其它语言类似,这两个操作有短路效应.也就是说,当判断式已经确定时 ...