自定义View基础和入门
http://blog.csdn.net/androidxiaogang/article/details/51849136
1、自定义View的步骤和原理
2、构造方法
自定义view的第一步是写构造方法,构造方法是用来初始化对象的,包括view也是对象。
构造方法在这里一般要写三个甚至四个,这样写的原因:我们在不同的情况下创建View的方式不同,可能需要从xml文件中填充布局,也可能不需要,或者也需要一样style之类的,因此不同情况下,使用的构造可能存在差异。因此构造方法也有这么多种类。从API上描述我们一定要有第二个构造方法。(在实际开发中也可以第一个调用第二个,第二个调用第三个构造,确保使用了每一种)
- 第一个构造:是在java创建视图的时候调用,如果从xml文件中填充,则不会调用这个构造方法;
- 第二个构造方法 :用于layout文件实例化,会把xml中的参数通过attrs带入
- 第三个构造方法:这个构造方法是在第二个基础上再传入style的
//是在java创建视图的时候调用,如果从xml文件中填充,则不会调用这个构造方法;
public MyView(Context context) {
super(context);
Log.i(TAG,"一个参数构造");
}
//用于layout文件实例化,会把xml中的参数通过attrs带入
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
Log.i(TAG, "二个参数构造");
}
//这个构造方法是在第二个基础上再传入style的。
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Log.i(TAG, "三个参数构造");
}
3、 onMeasure()方法
在上图中我们指定了
setMeasuredDimension(900000000, 900000000);
但在实际onlayout()中大小为10807552
1、onMeasure计算视图大小的过程
measure是测量的意思,那么onMeasure()方法顾名思义就是用于测量视图的大小的。View系统的绘制流程会从ViewRoot的performTraversals()方法中开始,在其内部调用View的measure()方法。measure()方法接收两个参数,widthMeasureSpec和heightMeasureSpec,这两个值分别用于确定视图的宽度和高度的规格和大小。
MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。specMode一共有三种类型,如下所示:
1.MeasureSpec.EXACTLY
”确定是“:表示父视图希望子视图大小应该是specSize中指定的大小
2.MeasureSpec.AT_MOST
“最大是”:子视图的大小最大是specSize中指定的大小。
3.MeasureSpec.UNSPECIFIED
“没有限制”:此时View的设计者可以根据自身的特性设置视图的大小。
/**
* <p>
* Measure the view and its content to determine the measured width and the
* measured height. This method is invoked by {@link #measure(int, int)} and
* should be overridden by subclasses to provide accurate and efficient
* measurement of their contents.
* </p>
*
* <p>
* <strong>CONTRACT:</strong> When overriding this method, you
* <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the
* measured width and height of this view. Failure to do so will trigger an
* <code>IllegalStateException</code>, thrown by
* {@link #measure(int, int)}. Calling the superclass'
* {@link #onMeasure(int, int)} is a valid use.
* </p>
*
* <p>
* The base class implementation of measure defaults to the background size,
* unless a larger size is allowed by the MeasureSpec. Subclasses should
* override {@link #onMeasure(int, int)} to provide better measurements of
* their content.
* </p>
*
* <p>
* If this method is overridden, it is the subclass's responsibility to make
* sure the measured height and width are at least the view's minimum height
* and width ({@link #getSuggestedMinimumHeight()} and
* {@link #getSuggestedMinimumWidth()}).
* </p>
*
* @param widthMeasureSpec horizontal space requirements as imposed by the parent.
* The requirements are encoded with
* {@link android.view.View.MeasureSpec}.
* @param heightMeasureSpec vertical space requirements as imposed by the parent.
* The requirements are encoded with
* {@link android.view.View.MeasureSpec}.
*
* @see #getMeasuredWidth()
* @see #getMeasuredHeight()
* @see #setMeasuredDimension(int, int)
* @see #getSuggestedMinimumHeight()
* @see #getSuggestedMinimumWidth()
* @see android.view.View.MeasureSpec#getMode(int)
* @see android.view.View.MeasureSpec#getSize(int)
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
它调用了一个getDefaultSize()的方法,再来看这个方法
/**
* Utility to return a default size. Uses the supplied size if the
* MeasureSpec imposed no constraints. Will get larger if allowed
* by the MeasureSpec.
*
* @param size Default size for this view
* @param measureSpec Constraints imposed by the parent
* @return The size this view should be.
*/
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;
}
通过onMeasure()的代码可以看出:这个方法的作用是根据父View中具体能够提供的空间大小来指定子View的视图大小,正常情况下,父View会满足子View所需要的大小,但是如果子View超过父View的最大空间,父View也只能给子View自已最大的空间。而不能无限制的满足子View。
4、onLayout()方法
onLayout()方法用于指定view在视图中的位置
/**
* Called from layout when this view should
* assign a size and position to each of its children.
*
* Derived classes with children should override
* this method and call layout on each of
* their children.
* @param changed This is a new size or position for this view
* @param left Left position, relative to parent
* @param top Top position, relative to parent
* @param right Right position, relative to parent
* @param bottom Bottom position, relative to parent
*/
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
5、为什么onMeasure(),onLayout()执行二次或者多次。
父视图可以不止一次的调用onMeasure(),onLayout()方法,如果子视图的个数为0,那么只会执行一次,如果不止一个的话,就可能执行2次或者多次。因为从xml文件或者用java代码添加子View的时候,父视图也会重新测量(给子view多大的空间)如上:第一次执行了onMeasure(),onLayout(),这时候view的宽高就发生了改变,会重新调用一次onMeasure()和onLayout()
6、onDraw()绘制内容
onDraw()把需要的内容,颜色,背景绘制到屏幕中。
demo代码
package view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
/**
* Created by yu on 2016/7/7.
*/
public class MyView extends View {
public static final String TAG = "MyView";
//是在java创建视图的时候调用,如果从xml文件中填充,则不会调用这个构造方法;
public MyView(Context context) {
super(context);
Log.i(TAG, "一个参数构造");
}
//用于layout文件实例化,会把xml中的参数通过attrs带入
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
Log.i(TAG, "二个参数构造");
}
//这个构造方法是在第二个基础上再传入style的。
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
Log.i(TAG, "三个参数构造");
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(400, 200);
Log.i(TAG, "onMeasure()");
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
Log.i(TAG, "onLayout()" + left + " : " + top + " : " + right + " : " + bottom);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.i(TAG, "onDraw()");
canvas.drawColor(Color.RED);
}
}