您的位置:首页 > 移动开发 > Android开发

android自定义View原理分析

2016-07-20 12:29 387 查看
自定义view
一.View的MeasureSpec

1.MeasureSpec包SpecMode和SpecSize。其中SpecMode包括

UNSPECIFIED:父容器不对该view做限制,要多大给多大,一般用于系统内部

EXACTLY:父容器已经得到该view的确切大小,对应于match_parent和给出确定大小值。

AT_MOST:父容器给出该view可能的最大尺寸,对应于wrap_content

2.一个View的大小由其MeasureSpec确定,而其MeasureSpec一般由该View的LayoutParams和父容器确定(父容器提供调用子view的measure()并传递过去MeasureSpec)。这里说一般是因为我们可以重写view的onMeasure()从而自己设置view的MeasureSpec

一个View的MeasureSpec的确定过程

childLayoutParams

parentSpecMode

EXACTLY

AT_MOST

UNSPECIFIED

dp/px

EXACTLY+childsize

EXACTLY+childSize

EXACTLY+childSize

Match_parent

EXACTLY+parentSize

AT_MOST+parentSize

UNSPECIFIED+0

Wrap_content

AT_MOST+parentSize

AT_MOST+parentSize

UNSPECIFIED+0

 

二.measure,layout,draw等简要分析

 



1. 从函数内部实现角度分析

view:

measure():调用onMeasure()

onMeasure(MeasureSpec):根据参数设置本view的宽高(调用setMeasureDimension
()方法)

layout(int):根据参数设置本view的位置

onLayout():空方法

draw():主要做四件事1.画背景2.画自己即调用自己的onDraw()3.
画子view即 调用dispatchDraw()4.画装饰

onDraw():空方法

 

ViewGroup:

measure():同view

onMeasure:同view

layout():同view(在ViewGroup中被标记为final)

onLayout():同view

draw():同view

onDraw():同view

measureChild():
获取到子view再根据ViewGroup的MeasureSpec和子view的
LayoutParams计算其MeasureSpec,然后调用子view的measure
(MeasureSpec)

可以看到ViewGroup在这里函数的实现上都是直接继承view的

 

2.从函数被赋予(期望)的功能的角度分析

view:

measure():调用onMeasure(),final无法重写

onMeasure(MeasureSpec):设置自己的宽高

layout(int):设置自己的位置

onLayout():空方法,子view不需要实现

draw():1.画背景,2.调用onDraw(),3.画修饰

onDraw():画view自己图案。

 

ViewGroup:

measure():调用onMeasure(),final无法被重写

onMeasure:1.遍历调用子view的measure()2.设置自己的宽高

layout():在ViewGroup中被标记为final

onLayout():设置子view的位置即遍历调用子view的layout()

draw():final不可重写,作用1.画背景,2.调用onDraw(),3.调用dispatchDraw()
画子view,4.画修饰

onDraw():我们可以重写该方法,当不要在该方法里去调用子view的draw(),因为
draw()里面已经这样做了,该方法的唯一作用是,如果我们想在ViewGroup里画一 些东西,比如背景,线条等,才来重写该方法画画。

 

三.measure祥解

ViewGroup的 measure


ViewGroup的measure()由外部调用,并且传入外界设置的宽高(MeasureSpec)。measure()是final的,所以不可重写。接着measure()会调用ViewGroup自己的onMeasure(),同时把measure()传进来的宽高传给它。ViewGroup是不会自动调用子view的measure()的,需要在ViewGroup的onMeasure()里自己调用子view的measure(),并且传进去期望的宽高。ViewGroup并没有重写onMeasure()

1.ViewGroup得到自己的宽高:通过measure()传进来的参数,在onMeasure()中得到

2.ViewGroup设置自己的宽高:通过重写onMeasure()

3.ViewGroup要得到所有子view的宽高:可以通过获取所有子view的layoutParams来计算。

4.ViewGroup设置子view的宽高:通过调用子view的measure()并传进去参数。当然,ViewGroup对子view设置的宽高只是子View的一个参考值,子view完全可以自己来定义宽高。

虽然ViewGroup没有重写view的onMeasure()方法,但是它提供了measureChild()和measureChildren()来对子view继续测量,其实内部就是获取到子view再计算其MeasureSpec(通过子view的LayoutParams,可以是通过xml得到)

,然后调用子view的measure()。

 


 

子View的measure


子view的measure()方法由外界调用,一般为其父容器,并且传进来宽高(MeasureSpec)。measure()是final,不能被重写。接着measure()会调用自己的onMeasure(),同时传进去宽高。从而确定view的宽高。在这里,我们可以重写onMeasure(),来设置view的宽高。

1.子View得到自己的宽高:通过measure()传进来的参数,在onMeasure()中得到

2.子View设置自己的宽高:通过重写onMeasure()

 

注意事项

1.重写view,如果要在xml布局文件里使用,并且允许使用wrap_content属性,则必须在重写onMeasure()时设定wrap_content的大小,否则就相当于match_parent(原因:当设置view的属性为wrap_content时,其SpecMode为AT_MOST,specSize为parentSize,逻辑上跟match_parent一样)做法如下:

 


其中mWidth和mHeight为原先设定的大小。

 

2.当view的onMeasure()执行完后,可以通过调用getMeasuredWidth/Height()来得到测量的结果。应该注意,Activity的生命周期和view的周期是不同步的,要先获得view的准确测量结果,可以通过Activity的onWindowFocusChanged()方法回调中获取,或者通过对view设置post。

 

 

3.view里面有一个重要的方法setWillNotDraw(boolean),当View不需要画任何东西时(一般是ViewGroup)可以吧它设置为true。view默认设置为false,ViewGroup默认设置为true

 

4.子view在onMeasure()里设置的宽高最好跟layout()里的t-b,r-l对应。虽然我们可以不这样做,当为避免混乱,最好做到一致,在不一致的情况下以layout()里的为准。getMeasureWidth/Height()获取的是onMeasure()里设置的宽高,getWidth/Height()获取的是最终的宽高但必须结果layout()。

 

5.一个view原始的onMeasure()和layout()必须被调用过,父ViewGroup的dispatchDraw()才会调用该view的onDraw()

6.自定义view的xx(Context context)构造函数为Java中创建view对象时使用

  xx(Context context,AttributeSet attrs)为在xml布局文件中使用

 

 

 

 

四.具体自定义过程及细节

Android的顶级view是DecorView(是一个FrameLayout),包括了整个屏幕。DecorView包含了一个LinearLayout,该LinearLayout的上面是titleBar,下面是一个id为content的view,就是我们在Activity中调用setContentView()时设置的view的位置。该view的宽为屏幕的宽度,mode为EXACTLY。高为屏幕除去titleBar后的高度,mode为EXACTLY

 

自定义view

1.重写onMeasure()

 


其中参数是MeasureSpec格式mode+size(具体查看表)该参数是根据父容器的MeasureSpec和该子view自身在xml里设置的宽高参数得到的(当然前提得是父容器是调用measureChild()来触发子view的onMeasure()的;或者父容器有根据自己的MeasureSpec和子view在xml里的设置改变传递给子view的MeasureSpec)

view在onMeasure()里要做的事情就是设置自己的宽高即调用setMeasuredDimension(int width ,int height)或者直接super.onMeasure()。而view的高宽是要根据父容器传给它的MeasureSpec来计算的。当SpecMode为EXACTLY时,直接把宽高设置为SpecSize就好。当SpecMode为AT_MOST时,此时的SpecSize为view所能设置的宽高的最大值,view一般在这种情况下回吧自己的尺寸设置为原先设置的默认尺寸。

 

2.重写layout()

子view的layout()一般不需要做太多修改,因为父容器已经给它设置好需要的位置。

 

3.重写onDraw()

子view的onDraw()功能和ViewGroup的不同,子view在这里要把自己的内容图案都画出来。

 

 

 

自定义ViewGroup

1.重写onMeasure()

 


其中参数是MeasureSpec格式mode+size(具体查看表)该参数是根据父容器的MeasureSpec和自身在xml里设置的宽高参数得到的(当然前提是父容器是调用measureChild()来触发子view的onMeasure()的;或者父容器有根据自己的MeasureSpec和子view在xml里的设置改变传递给子view的MeasureSpec)

 

ViewGroup在onMeasure()里面主要要做俩件事:

 

第一件事是触发所有子view的onMeasure():

在onMeasure()里面ViewGroup要先触发所有子view的onMeasure(),可以通过两种方法1.调用measureChildren(),然后等子view的onMeasure()执行后就可以调用子view的函数入getMeasureWidth/Height()等来获取测量结果。2.自己调用子view的measure(),然后传进去MeasureSpec参数,从而触发子view的onMeasure(),这里要注意的是在传给子view
  MeasureSepc的时候要根据ViewGroup自己的MeasureSpec和子view的LayoutParams设置好传递给子view的MeasureSpec(即measureChild()的原理)。

一般情况下推荐使用第1种方法。

 

第二件事是设置自己的宽和高:

设置自己的宽高可以调用setMeasuredDimension(int width ,int height);而设置宽高需要根据父容器传进来的MeasureSpec(包含了父容器和自身xml布局设置的影响)和所有子view的参数。当ViewGroup在xml布局里设置为Match_parent或者具体的数值时最好办,可以直接确定宽高;当ViewGroup在xml布局里设置为wrap_content时需要特殊处理(注意ViewGroup和View处理wrap_content是不同的),具体看该ViewGroup的功能。比如LinearLayout(vertical)在处理wrap_content的时候是获取所有子view的高度和margin,相加的值和parentSize比,然后选择小的那个设置为自己的高度。

 

2.重写onLayout()

ViewGroup的onLayout()是一个空实现,我们必须重写它。onLayout()其作用就是调用所有子view的layout()来设置子view的位置即左上角的坐标和右下角的坐标。在设置子view的位置的时候需要根据在子view的layoutParams和ViewGroup的特点。要做到子view的宽=|l
- r|,高=|t - b|

 

3.重写onDraw()

在ViewGroup中,onDraw()要做的事就是画自己的一些背景,线条之类的。不要在onDraw()里面其调用子view的draw(),因为在ViewGroup的draw()里已经遍历了子view的draw()了。

 

 

 

 

 

 

 

五.view的自定义属性步骤

1.在res/values里创建attrs.xml文件,再在里面声明属性,如下

 


其中sutext为属性名,string为属性的特性,而declare-styleable则声明了一个属性集,其name要与自定义的view的名字相同。

2.在布局文件中使用自定义属性

首先要声明命名空间

在Android studio中统一用如下格式,su可自定义

 


eclipse中用以下格式

 


后面为包名。

声明命名空间后就可以使用了

 


3.在view中获取自定义属性

 


 

 

 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: