View是屏幕善的一块矩形区域,它负责来显示一个区域,而且响应这个区域内的事件。能够说,手机屏幕上任意能够看得见的地方都是View。html
对于Activity来讲,咱们经过setContentView(view)添加的布局到Activity上,实际上都是添加到了Activity内部的DecorView上面,这个DecorView,其实就是一个FrameLayout,所以咱们的布局实际上添加到了FrameLayout里面。java
View的工做流程分为两部分,第一部分是显示在屏幕上的过程,第二部分是响应屏幕上的触摸事件的过程。android
对于显示在屏幕上的过程:是View从无到有,通过测量大小(Measure)、肯定在屏幕中的位置(Layout)、以及最终绘制在屏幕上(Draw)这一系列的过程。canvas
对于响应屏幕上的触摸事件的过程:则是Touch事件的分发过程。c#
Measure()、Layout()方法是final修饰的,没法重写,Draw()虽然不是final的,可是耶不建议重写该方法。app
View为咱们提供了onMeasure()、onLayout()、onDraw()这样的方法,其实所谓的自定义View,也就是对这三个方法的重写过程。less
View的onMeasure()方法以下:ide
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }onMeasure经过父View传递过来额大小和模式,以及自身的背景图片的大小得出自身最终的大小,经过setMeasuredDimension()方法设置给mMeasuredWidth和mMeasuredHeight。布局
普通的View的onMeasure逻辑大同小异,基本都是测量自身内容和背景,而后根据父View传递过来的MeasureSpec进行最终的大小断定,例如TextView会根据文字的长度,文字的大小,文字行高,文字的宽度,显示方法,背景图片以及父View传递过来的模式和大小最终肯定自身的大小。post
这是Android中本身定义的测量规格。由于测量过程当中,系统会把咱们代码或者布局中设置的View的LayoutParams转换成MeasureSpec,而后经过MeasureSpec来测量肯定View的宽高。
MeasureSpec由32位int值组成,高2位表示的测量模式specMode,后30位表明的是测量大小specSize
public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; /** 测量模式:父View不对子View的大小作任何限制,能够是子布局须要的任意大小 * Measure specification mode: The parent has not imposed any constraint * on the child. It can be whatever size it wants. */ public static final int UNSPECIFIED = 0 << MODE_SHIFT; /** 已经为子View给出了确切的值 至关于给View的LayoutParams指定了具体数值,或者match_parent. * Measure specification mode: The parent has determined an exact size * for the child. The child is going to be given those bounds regardless * of how big it wants to be. */ public static final int EXACTLY = 1 << MODE_SHIFT; /**子View能够跟本身的测量大小同样大。对应于View的LayoutParams的wrap_content * Measure specification mode: The child can be as large as it wants up * to the specified size. */ public static final int AT_MOST = 2 << MODE_SHIFT; /**根据 size和mode 去生成一个MeasureSpec值 * Creates a measure specification based on the supplied size and mode. * * The mode must always be one of the following: * <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li> * <li>{@link android.view.View.MeasureSpec#EXACTLY}</li> * <li>{@link android.view.View.MeasureSpec#AT_MOST}</li> * @return the measure specification based on size and mode */ public static int makeMeasureSpec(int size, int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } /**获取测量模式 * Extracts the mode from the supplied measure specification. * * @param measureSpec the measure specification to extract the mode from * @return {@link android.view.View.MeasureSpec#UNSPECIFIED}, * {@link android.view.View.MeasureSpec#AT_MOST} or * {@link android.view.View.MeasureSpec#EXACTLY} */ public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); } /**获取测量值大小 * Extracts the size from the supplied measure specification. * * @param measureSpec the measure specification to extract the size from * @return the size in pixels defined in the supplied measure specification */ public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); } static int adjust(int measureSpec, int delta) { return makeMeasureSpec(getSize(measureSpec + delta), getMode(measureSpec)); } }MeasureSpec是由父布局和View自身的LayoutParams来决定的
PS:通过实际使用,父View的布局为match_parent或指定具体数字时是EXACTLY;
父View的布局为wrap_content时是AT_MOST。
通过measure后,咱们就能够经过getMeasuredWidth/Height获取View的宽高。
普通的View对于layout方法直接空实现便可。
draw()的过程就是绘制View到屏幕上的规程,draw()的执行遵照以下步骤:
/* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. Draw the background(绘制背景) * 2. If necessary, save the canvas' layers to prepare for fading(保存画布的图层来准备色变) * 3. Draw view's content(绘制内容) * 4. Draw children(绘制children) * 5. If necessary, draw the fading edges and restore layers(画出褪色的边缘和恢复层) * 6. Draw decorations (scrollbars for instance)(绘制装饰 好比scollbar) */通常2和5是能够跳过的。
在TextView中在该方法中绘制文字、光标和CompoundDrawable、ImageView中相对简单,只是绘制了图片。View的绘制主要经过dispatchDraw()。
requestLayout - View从新调用一次layout过程。
invalidate - View从新调用一次draw过程。
postInvalidate - 在非UI线程发起invalidate动做。
forceLayout - 标识View在下一次重绘,须要从新调用layout过程。
一个自定义方块,循环显示不一样颜色
/** * Created by xupeng on 16/12/25. */ public class MyCustomView extends View { private Paint mPaint = null; private int[] colors = {Color.BLUE, Color.GREEN, Color.YELLOW}; public MyCustomView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView, 0, defStyleAttr); int color = typedArray.getColor(R.styleable.MyCustomView_custom_color, Color.BLUE); mPaint = new Paint(); mPaint.setColor(color); new Thread(new Runnable() { @Override public void run() { int i = 0; while (true){ mPaint.setColor(colors[i++ % 3]); postInvalidate(); try { Thread.sleep(1500); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // super.onMeasure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(getDefaultSize(getMinimumWidth(), widthMeasureSpec), getDefaultSize(getMinimumHeight(), heightMeasureSpec)); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); } @Override protected void onDraw(final Canvas canvas) { super.onDraw(canvas); canvas.drawRect(0, 0, 200, 300, mPaint); } }
View原理简介
http://blog.csdn.net/u011733020/article/details/50849475
帮你搞定Android自定义View
http://www.jianshu.com/p/84cee705b0d3
Android手把手教您自定义ViewGroup
http://blog.csdn.net/lmj623565791/article/details/38339817/
为何自定义ViewGroup ondraw方法不会被调用
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1014/1765.html
关于getMeasuredHeight和getHeight区别(getMeasureHeight是xml或代码中制定的测量高度,setMeasuredHeight; getHeight是实际显示出来的高度,经过view.layout()方即可以改变其值)