前言
Android的布局主要有三种形式,分别是xml方式、纯代码方式和混合方式(xml和代码)。但是在Android中拥有良好的xml布局方式,相信还是很大一部开发者应该是采用xml方式来布局的。如果简单的布局往往系统基本组件已足够,但是对于高追求的UI设计及特殊的业务来说,系统组件往往无法满足需求,因此自定义视图对于这些无法满足需求的功能视图来说多么的重要。
View介绍
View英文直译过来就是视图的意思。在Android中View是所有视图的基础类,在屏幕中View就是一块矩形区域,主要负责绘制这个区域和处理该区域的事件。View不仅仅是所有控件的基础类,起本身也是一个控件。
View的位置信息
View的位置主要由它的四个顶点来决定,分别对应View的四个属性:top、left、right、bottom;top是左上角纵坐标,left是左上角横坐标,right是右下角横坐标,bottom是右下角纵坐标,具体如下图:
自定义View的形式
View是所有视图的基础类,而继承至View的其他组件类多种多样。如:TextView、Button、LinearLayout、ListView和ViewGroup等等。由于组件的多样化,因此导致自定View的形式也分为多种情况,主要分为以下几种:
1、组合控件:将两个以上控件组合为一个新的控件;
2、继承控件:继承已有的控件(如:TextView、其他自定控件、Button和LinearLayout等),在原有控件基础上进行功能扩展;
3、继承View或ViewGroup:视图界面及功能完全自定义;
备注:以上不管哪种形式的自定义,最终都讲继承自View类。组合控件和继承控件,从本质来说都是属于第三方式,只是将继承于View并封装好的控件从新组合或拓展功能而已,而第三种方式就是最原始的组件形成方式。
自定义View的步骤
接下对于自定义View,将已第三种方式来阐述并举例说明。原因是第三方式基本覆盖了前面两张方式,且第三种方式是涉及到的自定义的内容最为全面的。本文在结束的的时候将会分别简单的例举一个例子去简要说明第一种和第二种方式。对于第三形式大致步骤如下:
备注:在自定义View中自定义属性并不是必须需要的。
接下来将依次都自定义View几大步骤进行说明。但其中对于自定义属性、对于onMeasure()方法和onLayout()方法中涉及的坐标的说明,请分别移驾之《Android 自定义View之属性(二)》和《Android 自定义View之坐标系(一)》;同时下文将不会对该两个内容点过多的阐述。
自定义View之构造方法
当定义类继承于View或ViewGroup时,系统都会自动提示我们重写它的构造方法。View和ViewGroup中分别有四个构造方法可以重写,每个构造方法用于的地方及涉及到的功能都有所不同,一下将分别介绍。
带一个参数的构造方法
//该构造方法只有一个Context类型参数。
//在Java代码中使用,当在Java代码中直接通过new形式创建的是时候,就会调用这个构造方法。
/**
* @param context
*/
public CustView(Context context) {
super(context);
}
带二个参数的构造方法
//该构造方法有两个参数,分别为:Context和AttributeSet属性集
//在布局中使用,当在xml布局使用时,就会调用这个构造方法
/**
* @param context
* @param attrs:该对象就是这个控件中定义的所有属性
*/
public CustView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
带三个参数的构造方法
//这个构造方法有三个参数:Context、AttributeSet属性集和defStyleAttr自定义属性的引用;
//该构造方法不会被默认调用,必须要手动调用且第二个构造函数中调用,主要用于有默认style时。
/**
* @param context
* @param attrs
* @param defStyleAttr
*/
public CustView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
带四个参数的构造方法
//这个构造方法有四个参数:Context、AttributeSet属性集、defStyleAttr自定义属性的引用和defStyleRes一个指向Style的
//资源ID;
//该构造方法也不会被默认调用,必须要手动调用且第二个构造函数中调用,主要用于有默认style时。且只有在API版本>21时
//才会用到;
/**
* @param context
* @param attrs
* @param defStyleAttr
* @param defStyleRes
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public CustView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
自定义View之onMeasure方法(控件宽高测量)
onMeasure方法主要功能是用于测量并决定控件本身及其子View的宽高。但由于onMeasure()方法可能会被多次调用,主要是控件中本身或子View可能对分配给自己的空间不合适,因此需向父空间申请重新分配空间,因此在onMeasure()使用测量功能是不太准确的(所以建议在onLayout或onDraw中重新测量)。
在onMeasure()方法中,包含测量并决定控件宽高的信息,主要通过onMeasure方法提供的两个参数widthMeasureSpec
和heightMeasureSpec
结合View的静态内部类MeasureSpec
相关方法获取的。widthMeasureSpec和heightMeasureSpec两个参数包含的内容,主要为测量模式
和具体测量值
。测量模式主要分为三种情况,而测量值是依照这三种情况而获取的。
widthMeasureSpec和heightMeasureSpec参数说明
widthMeasureSpec和heightMeasureSpec两个参数包含了该控件的宽高的信息,主要内容为测量模式
和具体测量值
,这两个参数是一个32位int类型值,前2位表示模式(mode)后30位表示大小(size)。
测量模式的三种情况:
测量模式 | 最高两位内容 | 模式说明 |
---|---|---|
EXACTLY | 最高两位是01 | 当宽高设置为具体值时使用,如100dp、match_parent等,此时取出的尺寸是精确的尺寸;在这种模式下,尺寸的值是多少,那么这个组件的长或宽就是多少。 |
AT_MOST | 最高两位是11 | 当宽高值设置为wrap_content时使用,此时取出的尺寸是控件最大可获得的空间,也就是父组件能够给出的某个最大的空间,当前组件的长或宽最大只能为这么大,当然也可以比这个小。 |
UNSPECIFIED | 最高两位是00 | 当没有指定宽高值时使用,即当前组件,可以随便用空间,不受限制 |
MeasureSpec
MeasureSpec是View的静态内部类,该主要用于解析widthMeasureSpec和heightMeasureSpec两个参数的内容。通俗来说View为这两个参数提供了一个解析封装工具类。如下为该类提供的几个主要方法:
//该方法主要用于封装widthMeasureSpec和heightMeasureSpec两个参数,由给出的尺寸大小和模式生成一个包含这两个信息
//的int变量。
/*
* size 控件大小,即模型后30位内容
* mode 控件模式,即模型前2位内容
*
*/
public static int makeMeasureSpec(int size, int mode)
//该方法主要用于解析模式信息,将得到的值与三个常量进行比较。
/*
* measureSpec 测量规范信息
*/
public static int getMode(int measureSpec)
//该方法主要用于解析模式大小值,将得到尺寸大小的值。
/*
* measureSpec 测量规范信息
*
*/
public static int getSize(int measureSpec)
EXP,以下就是使用MeasureSpec工具类的实例方法:
//未使用setMeasuredDimension(width, height)方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 获取测量模式(Mode)
int specModeWidth = MeasureSpec.getMode(widthMeasureSpec);
int specModeHeight = MeasureSpec.getMode(heightMeasureSpec);
// 获取测量大小(Size)
int specSizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int specModeHeight = MeasureSpec.getMode(heightMeasureSpec);
// 通过Mode 和 Size 生成新的SpecMode
int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);
}
//使用了setMeasuredDimension(width, height)方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 获取测量模式(Mode)
int specModeWidth = MeasureSpec.getMode(widthMeasureSpec);
int specModeHeight = MeasureSpec.getMode(heightMeasureSpec);
// 获取测量大小(Size)
int specSizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int specModeHeight = MeasureSpec.getMode(heightMeasureSpec);
// 通过Mode 和 Size 生成新的SpecMode
int measureSpecWidth=MeasureSpec.makeMeasureSpec(size, mode);
int measureSpecHeight=MeasureSpec.makeMeasureSpec(size, mode);
setMeasuredDimension(measureSpec, measureSpecHeight);
}
备注:在View类如果不重写onMeasure的话就只支持EXACTLY模式。还有在重新设置控件的宽高时,如果使用了setMeasuredDimension(width, height)方法,那么就一定要要删除“super. onMeasure(widthMeasureSpec, heightMeasureSpec);”这行代码,不然会报错。
注意:对于View来说用来计算View的宽高,对于ViewGroup来说,除了要测量自身宽高,还需要测量子View的宽高。
自定义View继承于ViewGroup
如果自定义View继承于ViewGroup那么除了要测量自身宽高,还需要测量子View的宽高。其所涉及的到的方法如下:
//获取子View的数量
public int getChildCount() {
return mChildren.size();
}
//获取第i个子控件
public View getChildAt(int index) {
if (index < 0 || index >= mChildrenCount) {
return null;
}
return mChildren[index];
}
//LayoutParams.width和LayoutParams.height:设置或获取子控件的宽或高;
public ViewGroup.LayoutParams getLayoutParams() {
return mLayoutParams;
}
//测量子View的宽高;
protected void measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
注意,以上几个方法是ViewGroup继承View类后扩展的方法。
测量流程
自定义View之onLayout方法
onLayout()主要用于决定控件的位置,即布局控件在父View的位置。跟onMeasure类似,onLayout也会根据View的类型分成两种情况进行处理,如果是继承View计算控件本身就足够了;如果是继承于ViewGroud,想计算整个View树的位置,就需要递归的去计算每一个子视图的位置。
/**
* @param changed 判断是否是一个新的尺寸或位置是否发生变化
* @param left 分别表示这个View相对于父布局的左边距离
* @param top 分别表示这个View相对于父布局的上边距离
* @param right 分别表示这个View相对于父布局的右边距离
* @param bottom 分别表示这个View相对于父布局的下边距离
*/
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
注意:且继承于ViewGroup不能调用super.onLayout(changed, l, t, r, b)。
自定义View继承于ViewGroup
如果自定义View继承于ViewGroup那么除了要计算自身位置,还需要测量子View的位置。其所涉及的到的方法如下:
//获取子View的数量
public int getChildCount() {
return mChildren.size();
}
//获取第i个子控件
public View getChildAt(int index) {
if (index < 0 || index >= mChildrenCount) {
return null;
}
return mChildren[index];
}
//LayoutParams.width和LayoutParams.height:设置或获取子控件的宽或高;
public ViewGroup.LayoutParams getLayoutParams() {
return mLayoutParams;
}
//设置子View布局的上下左右边的坐标。
@Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
注意,以上几个方法是ViewGroup继承View类后扩展的方法。
位置计算流程图
常复写于viewGroup的自定义子类。它有负责对它内部所有children进行处理,告知childrenView的位置,以正确摆放。ViewGroup中onLayout是抽象方法必须复写,这是children位置能正确摆放的保证。依靠mLeft,mTop,mRight,mBottom这四个值,以坐上为原点,这四个值分别为对应边到原点的距离。最后和onMeasure一样,记得调用child.layout()方法。
注意:对于View来说用来计算View的位置参数,对于ViewGroup来说,除了要测量自身位置,还需要测量子View的位置。
自定义View之onDraw方法
onDraw()方法主要用于绘制View的内容,也就是最终呈现给用户使用的界面。在解析介绍onDraw()我先查看一下draw()方法的源码:
public void draw(Canvas canvas) {
......
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
......
}
通过以上的源码,可以看出自定义View的绘制界面有6个步骤,分别为:
第一步骤:绘制背景。
第二步骤:是否保存当前canvas。
第三步骤:绘制View的内容,即当前控件的onDraw()。
第四步骤:绘制子View。
第五步骤:是否绘制边缘、阴影等效果。
第六步骤:是否绘制装饰,如滚动条等。
而onDraw()方法是六个步骤中的第三步,用于绘制界面具体内容。而在这个方法中有一个参数Canvas,其实整个View的绘制工作就是由Canvas类和Paint类来完成的。
Canvas类
Canvas用于托管绘图,可以使用Paint画笔对象在画布绘制很多东西,即画布。
如果对于Canvas需要详细了解或有兴趣的可以移步《Android Canvas方法总结》,下面只是简单归纳一下使用到的方法及说明:
//绘制过程中需要用到的绘图基元(即绘制相关)
1) drawArc()//绘制圆弧;
2) drawBitmap()//绘制Bitmap图像;
3) drawCircle()//绘制圆圈;
4) drawLine()//绘制线条;
5) drawOval()//绘制椭圆;
6) drawPath()//绘制Path路径;
7) drawPicture()//绘制Picture图片;
8) drawRect()//绘制矩形;
9) drawRoundRect()//绘制圆角矩形;
10) drawText()//绘制文本;
11) drawVertices()//绘制顶点。
//与层的保存和回滚相关的方法;
1) canvas.save()//把当前绘制的图像保存起来,让后续的操作相当于是在一个新图层上绘制;
2) canvas.restore()//把当前画布调整到上一个save()之前的状态;
//对画布进行操作的方法
1) canvas.translate(dx, dy)//把当前画布的原点移到(dx, dy)点,后续操作都以(dx, dy)点作为参照;
2) canvas.scale(x, y)//将当前画布在水平方向上缩放x倍,竖直方向上缩放y倍;
3) canvas.rotate(angle)//将当前画布顺时针旋转angle度。
Paint类
Paint描述绘图的颜色和样式,即画笔对象。这个类中包含了如何绘制几何图形、文字和位图的样式和颜色信息,指定了如何绘制文本和图形。画笔对象右很多设置方法,大体上可以分为两类:一类与图形绘制有关,一类与文本绘制有关。
如果对Paint类想进一步了解,可以移步《Paint类全解析》。下面只是简单归纳一下使用到的方法及说明:
//图形绘制之画笔设置
//设置绘制的颜色,a表示透明度,r、g、b表示颜色值;
1) setArgb(int a, int r, int g, int b)
//设置绘制的图形的透明度;
2) setAlpha(int a)
//设置绘制的颜色;
3) setColor(int color)
//设置是否使用抗锯齿功能,抗锯齿功能会消耗较大资源,绘制图形的速度会减慢;
4) setAntiAlias(boolean a)
//设置是否使用图像抖动处理,会使图像颜色更加平滑饱满,更加清晰;
5) setDither(boolean b)
//设置是否在动画中滤掉Bitmap的优化,可以加快显示速度;
6) setFileterBitmap(Boolean b)
//设置MaskFilter来实现滤镜的效果;
7) setMaskFilter(MaskFilter mf)
//设置颜色过滤器,可以在绘制颜色时实现不同颜色的变换效果;
8) setColorFilter(ColorFilter cf)
//设置绘制的路径的效果;
9) setPathEffect(PathEffect pe)
//设置Shader绘制各种渐变效果;
10) setShader(Shader s)
//在图形下面设置阴影层,r为阴影角度,x和y为阴影在x轴和y轴上的距离,c为阴影的颜色;
11) setShadowLayer(float r, int x, int y, int c)
//设置画笔的样式:FILL实心;STROKE空心;FILL_OR_STROKE同时实心与空心;
12) setStyle(Paint.Style s)
//当设置画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式;
13) setStrokeCap(Paint.Cap c)
//设置绘制时各图形的结合方式;
14) setStrokeJoin(Paint.Join j)
//当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的粗细度;
15) setStrokeWidth(float w)
//设置图形重叠时的处理方式;
16) setXfermode(Xfermode m)
//文本绘制之画笔设置
//设置绘制的文本的对齐方式;
1) setTextAlign(Path.Align a)
//设置文本在X轴的缩放比例,可以实现文字的拉伸效果;
2) setTextScaleX(float s)
//设置字号;
3) setTextSize(float s)
//设置斜体文字,s是文字倾斜度;
4) setTextSkewX(float s)
//设置字体风格,包括粗体、斜体等;
5) setTypeFace(TypeFace tf)
//设置绘制的文本是否带有下划线效果;
6) setUnderlineText(boolean b)
//设置绘制的文本是否带有删除线效果;
7) setStrikeThruText(boolean b)
//模拟实现粗体文字,如果设置在小字体上效果会非常差;
8) setFakeBoldText(boolean b)
//如果设置为true则有助于文本在LCD屏幕上显示效果;
9) setSubpixelText(boolean b)
//画笔其他功能型方法
//清除阴影层;
1) clearShadowLayer()
//重置画笔为默认值。
2) reset()
//将页面中t文本从s下标开始到e下标结束的所有字符所占的区域宽高封装到b这个矩形中;
3) getTextBounds(String t, int s, int e, Rect b)
//返回t文本中从s下标开始到e下标结束的所有字符所占的宽度;
4) measureText(String t, int s, int e)
到此自定义View的基本步骤内容基本讲述完毕,一下将简单描述一下自定义View其他相关方法。
自定义View其他方法
onTouchEvent()
onTouchEvent方法针对用户触摸操作事件处理。我们通过方法中MotionEvent参数对象的getAction()方法来实时获取用户的手势,有UP、DOWN和MOVE等枚举值。分别表示用于手指抬起、按下和滑动等动作。
invalidate()
invalidate()方法的作用是对View进行重绘,即invalidate方法则只会导致View的onDraw方法被调用,如果视图的大小发生了变化,还会调用layout()方法。
postInvalidate()
功能与invalidate()方法相同,postInvalidate()方法是异步请求重绘视图。
requestLayout()
requestLayout()方法是对View重新执行布局layout过程,即requestLayout方法会导致View的onMeasure、onLayout、onDraw方法被调用,有可能不会调用draw()过程(即不会重新绘制任何视图),包括该调用者本身。对于会不会调用draw,取决于view的l,t,r,b是否改变。
requestFocus()
重新执行View的draw()过程,但只会绘制需要重绘的视图,即哪个View或ViewGroup调用了这个方法,就重绘哪个视图。
自定义执行流程
自定义View的实现(简单例子)
1、创建自定义View类的创建
对于自定义View类的构造函数,不管是继承于View还是ViewGroup或者其他组件,都是一样的。
1、继承于View
public class CustView extends View {
......
public CustView(Context context) {
super(context);
}
public CustView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public CustView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
......
}
2、继承于ViewGroup
public class CustView extends ViewGroup {
......
public CustView(Context context) {
super(context);
}
public CustView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public CustView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
......
}
2、创建Attr属性及属性实现
对于自定义View的属性的实现方式及内容,与不管是继承于View还是ViewGroup或者其他组件,都是一样的。
1、首先在value文件下创建attrs.xml,并在attrs.xml文件中编写属性内容
<resources>
<declare-styleable name="CustViewStye">
<attr name="attr1" format="string" />
</declare-styleable>
</resources>
2、在相关页面的布局中使用
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:cust="http://schemas.android.com/apk/res-auto"//此处次声明自定属性需要用到的命名空间
android:layout_width="match_parent"
android:layout_height="match_parent" >
在布局中声明属性
<com.example.main.CustView
android:layout_width="100dp"
android:layout_height="100dp"
cust:attr1="@string/hello_world" />//此处为attr1属性赋值
</RelativeLayout>
3、在自定义View的第第二个或第三及第四个构造方法中获取属性值
public CustView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//通过obtainStyledAttributes方法将属性集转化为TypedArray(键值对)
TypedArray typeArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustViewStye,
0, 0);
//依据属性名称,找到对应的属性值。
String attr1 = typeArray.getString(R.styleable.CustViewStye_attr1);
//使用完TypedArray一定记得的释放TypedArray,不然会照成内存泄漏
typeArray.recycle();
}
3、onMeasure方法重写
在自定义View类中从写onMeasure方法
1、继承于View
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 获取测量模式(Mode)
int specModeWidth = MeasureSpec.getMode(widthMeasureSpec);
int specModeHeight = MeasureSpec.getMode(heightMeasureSpec);
// 获取测量大小(Size)
int specSizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int specSizeHeight = MeasureSpec.getSize(heightMeasureSpec);
}
2、继承于ViewGroup
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 获取测量模式(Mode)
int specModeWidth = MeasureSpec.getMode(widthMeasureSpec);
int specModeHeight = MeasureSpec.getMode(heightMeasureSpec);
// 获取测量大小(Size)
int specSizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int specSizeHeight = MeasureSpec.getSize(heightMeasureSpec);
//测量所有子控件的宽和高,只有先测量了所有子控件的尺寸,后面才能使用child.getMeasuredWidth()
for (int i = 0; i < getChildCount(); ++i) {
final View child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
//测量某一个子控件宽高
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
4、onLayout方法重写
1、继承于View
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
layout(left, top, right, bottom);
}
2、继承于ViewGroup
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 记录总高度
int mTotalHeight = 0;
// 遍历所有子视图
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
// 获取在onMeasure中计算的视图尺寸
int measureHeight = childView.getMeasuredHeight();
int measuredWidth = childView.getMeasuredWidth();
childView.layout(l, mTotalHeight, measuredWidth, mTotalHeight + measureHeight);
mTotalHeight += measureHeight;
}
}
注意:且继承于ViewGroup不能调用super.onLayout(changed, l, t, r, b)。
5、onDraw方法重写
//在自定义View的区域内画一个宽为width和高为height的矩形
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//获取各个编剧的padding值
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
//获取绘制的View的宽度和高度
int width = getWidth() - (paddingLeft+paddingRight);
int height = getHeight() - (paddingTop+paddingBottom);
//绘制View,左上角坐标(paddingLeft,paddingTop),右下角坐标(width+paddingLeft,height+paddingTop)
canvas.drawRect(paddingLeft,paddingTop,width+paddingLeft,height+paddingTop,mPaint);
}
6、其他方法及功能实现
//重写该方法,可以用来处理用户的操作View区域内的一些事件。如:触摸、滑动、缩放等
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
//在定义view中无须再使用Handler,可以直接View已有的post系列方法代替
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
结束
1、自定义View的时候一定要注意继承于View还是ViewGroup,这将影响后面对于View的测量和位置的设定。
2、没有必要的情况下就不要在View中使用Handler,因为View中已经提供了post系列方法,完全可以替代Handler的作用。
3、View单有嵌套事件需要处理时,一定要分析好在哪一层做什么事件接受,在哪一层做事件拦截等。
4、尽量处理好临时对象,因为有大量的临时对象,就会引起内存抖动,影响View的效果。
5、区分invalidate()和requestLayout(),并在特定环境下有效的利用起来。