1　、概述　Android　自定义View　/　ViewGroup　的步骤大致如下:　1)　自定义属性;2)　选择和设置构造方法;3)　重写onMeasure()方法;4)　重写onDraw()方法;5)　重写onLayout()方法;6)　重写其他事件的方法(滑动监听等)。　2、自定义属性　Android自定义属性主要有定义、使用和获取三个步骤。　2.1、定义自定义属性　我们通常将自定义属性定义在/values/attr.xml文件中(attr.xml文件需要自己创建)。　先来看一段示例代码:　　<　resources　>　<　attr　name　="rightPadding"　format　="dimension"　/>　<　declare-styleable　name　="CustomMenu"　>　<　attr　name　="rightPadding"　/>　　　可以看到,我们先是定义了一个属性rightPadding,然后又在CustomMenu中引用了这个属性。下面说明一下:　首先,我们可以在declare-stylable标签中直接定义属性而不需要引用外部定义好的属性,但是为了属性的重用,我们可以选择上面的这种方法:先定义,后引用;　declare-stylable标签只是为了给自定义属性分类。一个项目中可能又多个自定义控件,但只能又一个attr.xml文件,因此我们需要对不同自定义控件中的自定义属性进行分类,这也是为什么declare-stylable标签中的name属性往往定义成自定义控件的名称;　所谓的在declare-stylable标签中的引用,就是去掉了外部定义的format属性,如果没有去掉format,则会报错;如果外部定义中没有format而在内部引用中又format,也一样会报错。　常用的format类型:　1)　string:字符串类型;2)　integer:整数类型;3)　float:浮点型;4)　dimension:尺寸,后面必须跟dp、dip、px、sp等单位;5)　Boolean:布尔值;6)　reference:引用类型,传入的是某一资源的ID,必须以“@”符号开头;7)　color:颜色,必须是“#”符号开头;8)　fraction:百分比,必须是“%”符号结尾;9)　enum:枚举类型　下面对format类型说明几点:　format中可以写多种类型,中间使用“|”符号分割开,表示这几种类型都可以传入这个属性;　enum类型的定义示例如下代码所示:　<　resources　>　<　attr　name　="orientation"　>　<　enum　name　="horizontal"　value　="0"　/>　<　enum　name　="vertical"　value　="1"　/>　　<　declare-styleable　name　="CustomView"　>　<　attr　name　="orientation"　/>　　　使用时通过getInt()方法获取到value并判断,根据不同的value进行不同的操作即可。　2.2、使用自定义属性　在XML布局文件中使用自定义的属性时,我们需要先定义一个namespace。Android中默认的namespace是android,因此我们通常可以使用“android:xxx”的格式去设置一个控件的某个属性,android这个namespace的定义是在XML文件的头标签中定义的,通常是这样的:　xmlns:android="http://schemas.android.com/apk/res/android"　我们自定义的属性不在这个命名空间下,因此我们需要添加一个命名空间。　自定义属性的命名空间如下:　xmlns:app="http://schemas.android.com/apk/res-auto"　可以看出来,除了将命名空间的名称从android改成app之外,就是将最后的“res/android”改成了“res-auto”。　注意:　自定义namespace的名称可以自己定义,不一定非得是app。　2.3、获取自定义属性　在自定义View　/　ViewGroup中,我们可以通过TypedArray获取到自定义的属性。示例代码如下:　public　CustomMenu(Context　context,　AttributeSet　attrs,　int　defStyleAttr)　{　super　(context,　attrs,　defStyleAttr);　TypedArray　a　=　context.getTheme().obtainStyledAttributes(attrs,　R.styleable.CustomMenu,　defStyleAttr,　0　);　int　indexCount　=　a.getIndexCount();　for　(　int　i　=　0;　i　<　indexCount;　i++　)　{　int　attr　=　a.getIndex(i);　switch　(attr)　{　case　R.styleable.CustomMenu_rightPadding:　mMenuRightPadding　=　a.getDimensionPixelSize(attr,　0　);　break　;　}　}　a.recycle();}　这里需要说明一下:　获取自定义属性的代码通常是在三个参数的构造方法中编写的(具体为什么是三个参数的构造方法,下面的章节中会有解释);　在获取TypedArray对象时就为其绑定了该自定义View的自定义属性集(CustomMenu),通过getIndexCount()方法获取到自定义属性的数量,通过getIndex()方法获取到某一个属性,最后通过switch语句判断属性并进行相应的操作;　在TypedArray使用结束后,需要调用recycle()方法回收它。　3、构造方法　当我们定义一个新的类继承了View或ViewGroup时,系统都会提示我们重写它的构造方法。View　/　ViewGroup中又四个构造方法可以重写,它们分别有一、二、三、四个参数。四个参数的构造方法我们通常用不到,因此这个章节中我们主要介绍一个参数、两个参数和三个参数的构造方法(这里以CustomMenu控件为例)。　3.1、一个参数的构造方法　构造方法的代码:　public　CustomMenu(Context　context)这个构造方法只有一个参数Context上下文。当我们在JAVA代码中直接通过new关键在创建这个控件时,就会调用这个方法。　3.2、两个参数的构造方法　构造方法的代码:　public　CustomMenu(Context　context,　AttributeSet　attrs)这个构造方法有两个参数:Context上下文和AttributeSet属性集。当我们需要在自定义控件中获取属性时,就默认调用这个构造方法。AttributeSet对象就是这个控件中定义的所有属性。　我们可以通过AttributeSet对象的getAttributeCount()方法获取属性的个数,通过getAttributeName()方法获取到某条属性的名称,通过getAttributeValue()方法获取到某条属性的值。　注意:　不管有没有使用自定义属性,都会默认调用这个构造方法,“使用了自定义属性就会默认调用三个参数的构造方法”的说法是错误的。　3.3、三个参数的构造方法　构造方法的代码:　public　CustomMenu(Context　context,　AttributeSet　attrs,　int　defStyleAttr)这个构造方法中有三个参数:Context上下文、AttributeSet属性集和defStyleAttr自定义属性的引用。这个构造方法不会默认调用,必须要手动调用,这个构造方法和两个参数的构造方法的唯一区别就是这个构造方法给我们默认传入了一个默认属性集。　defStyleAttr指向的是自定义属性的标签中定义的自定义属性集,我们在创建TypedArray对象时需要用到defStyleAttr。　3.4、三个构造方法的整合　一般情况下,我们会将这三个构造方法串联起来,即层层调用,让最终的业务处理都集中在三个参数的构造方法。我们让一参的构造方法引用两参的构造方法,两参的构造方法引用三参的构造方法。示例代码如下:　public　CustomMenu(Context　context)　{　this　(context,　null　);}　public　CustomMenu(Context　context,　AttributeSet　attrs)　{　this　(context,　attrs,　0　);}　public　CustomMenu(Context　context,　AttributeSet　attrs,　int　defStyleAttr)　{　super　(context,　attrs,　defStyleAttr);　//　业务代码　}　这样一来,就可以保证无论使用什么方式创建这个控件,最终都会到三个参数的构造方法中处理,减少了重复代码。　4、onMeasure()　onMeasure()方法中主要负责测量,决定控件本身或其子控件所占的宽高。我们可以通过onMeasure()方法提供的参数widthMeasureSpec和heightMeasureSpec来分别获取控件宽度和高度的　测量模式　和　测量值　(测量　=　测量模式　+　测量值)。　widthMeasureSpec和heightMeasureSpec虽然只是int类型的值,但它们是通过MeasureSpec类进行了编码处理的,其中封装了测量模式和测量值,因此我们可以分别通过MeasureSpec.getMode(xMeasureSpec)和MeasureSpec.　getSize(xMeasureSpec)来获取到控件或其子View的测量模式和测量值。　测量模式分为以下三种情况:　1)　EXACTLY:当宽高值设置为具体值时使用,如100DIP、match_parent等,此时取出的size是精确的尺寸;2)　AT_MOST:当宽高值设置为wrap_content时使用,此时取出的size是控件最大可获得的空间;3)　UNSPECIFIED:当没有指定宽高值时使用(很少见)。　onMeasure()方法中常用的方法:　1)　getChildCount():获取子View的数量;2)　getChildAt(i):获取第i个子控件;3)　subView.getLayoutParams().width/height:设置或获取子控件的宽或高;4)　measureChild(child,　widthMeasureSpec,　heightMeasureSpec):测量子View的宽高;5)　child.getMeasuredHeight/width():执行完measureChild()方法后就可以通过这种方式获取子View的宽高值;6)　getPaddingLeft/Right/Top/Bottom():获取控件的四周内边距;7)　setMeasuredDimension(width,　height):重新设置控件的宽高。如果写了这句代码,就需要删除“super.　onMeasure(widthMeasureSpec,　heightMeasureSpec);”这行代码。　注意:　onMeasure()方法可能被调用多次,这是因为控件中的内容或子View可能对分配给自己的空间“不满意”,因此向父空间申请重新分配空间。　5、onDraw()　onDraw()方法负责绘制,即如果我们希望得到的效果在Android原生控件中没有现成的支持,那么我们就需要自己绘制我们的自定义控件的显示效果。　要学习onDraw()方法,我们就需要学习在onDraw()方法中使用最多的两个类:Paint和Canvas。　注意:　每次触摸了自定义View/ViewGroup时都会触发onDraw()方法。　5.1、Paint类Paint画笔对象,这个类中包含了如何绘制几何图形、文字和位图的样式和颜色信息,指定了如何绘制文本和图形。画笔对象右很多设置方法,大体上可以分为两类:一类与图形绘制有关,一类与文本绘制有关。　Paint　类中有如下方法:　1、图形绘制:　1)　setArgb(int　a,　int　r,　int　g,　int　b):设置绘制的颜色,a表示透明度,r、g、b表示颜色值;2)　setAlpha(int　a):设置绘制的图形的透明度;3)　setColor(int　color):设置绘制的颜色;4)　setAntiAlias(boolean　a):设置是否使用抗锯齿功能,抗锯齿功能会消耗较大资源,绘制图形的速度会减慢;5)　setDither(boolean　b):设置是否使用图像抖动处理,会使图像颜色更加平滑饱满,更加清晰;6)　setFileterBitmap(Boolean　b):设置是否在动画中滤掉Bitmap的优化,可以加快显示速度;7)　setMaskFilter(MaskFilter　mf):设置MaskFilter来实现滤镜的效果;8)　setColorFilter(ColorFilter　cf):设置颜色过滤器,可以在绘制颜色时实现不同颜色的变换效果;9)　setPathEffect(PathEffect　pe):设置绘制的路径的效果;10)　setShader(Shader　s):设置Shader绘制各种渐变效果;11)　setShadowLayer(float　r,　int　x,　int　y,　int　c):在图形下面设置阴影层,r为阴影角度,x和y为阴影在x轴和y轴上的距离,c为阴影的颜色;12)　setStyle(Paint.Style　s):设置画笔的样式:FILL实心;STROKE空心;FILL_OR_STROKE同时实心与空心;13)　setStrokeCap(Paint.Cap　c):当设置画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式;14)　setStrokeJoin(Paint.Join　j):设置绘制时各图形的结合方式;15)　setStrokeWidth(float　w):当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的粗细度;16)　setXfermode(Xfermode　m):设置图形重叠时的处理方式;　2、文本绘制:　1)　setTextAlign(Path.Align　a):设置绘制的文本的对齐方式;2)　setTextScaleX(float　s):设置文本在X轴的缩放比例,可以实现文字的拉伸效果;3)　setTextSize(float　s):设置字号;4)　setTextSkewX(float　s):设置斜体文字,s是文字倾斜度;5)　setTypeFace(TypeFace　tf):设置字体风格,包括粗体、斜体等;6)　setUnderlineText(boolean　b):设置绘制的文本是否带有下划线效果;7)　setStrikeThruText(boolean　b):设置绘制的文本是否带有删除线效果;8)　setFakeBoldText(boolean　b):模拟实现粗体文字,如果设置在小字体上效果会非常差;9)　setSubpixelText(boolean　b):如果设置为true则有助于文本在LCD屏幕上显示效果;　3、其他方法:　1)　getTextBounds(String　t,　int　s,　int　e,　Rect　b):将页面中t文本从s下标开始到e下标结束的所有字符所占的区域宽高封装到b这个矩形中;2)　clearShadowLayer():清除阴影层;3)　measureText(String　t,　int　s,　int　e):返回t