View简介
View体系是整个Android中很大分量的一部分,也是最直观的体现(用户界面控件).View是整个View体系中所有控件的基类,其子类控件包括有ViewGroup、TextureView、SurfaceView、ImageView等.
安卓中的坐标系
说到View,不得不说一下Android的坐标系
1.屏幕坐标系
屏幕坐标系是以手机屏幕的左上角顶点为原点(0,0),向右为x轴正方向,向下为y轴正方向的坐标系

以下的两个获取坐标的方法就是基于屏幕坐标系的
getRawX(); //获取子View左上角距离屏幕X轴的距离
getRawY(); //获取子View左上角距离屏幕Y轴的距离
2.View的坐标系
我们知道,ViewGroup是可以多级嵌套使用的,也就是说,一个控件可以最外面是一个ViewGroup A,A中呢又有一层ViewGroup B,而B中有一个View C.那么C的坐标系呢,就是相对于其父控件ViewGroup B的.

图画的有点丑,但是应该还算清晰(如果有好用的画图软件的话,劳驾推荐一波).
以下获取坐标值的方法就是基于View的坐标系的,那么在上图这个例子中,就是子控件C相对于父控件B的坐标系的坐标值的获取方法
getLeft(); //ViewC的左上角相对父控件B的x轴的距离
getRight(); //ViewC的右下角相对父控件B的x轴的距离
getTop(); //ViewC的左上角相对父控件B的y轴的距离
getBottom(); //ViewC的右下角相对父控件B的y轴的距离
好了,基础的简介就到这里,我们开始进入源码分析
首先呢,我们看View的声明:
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource
先看看Drawable.Callback
public interface Callback {
void invalidateDrawable(@NonNull Drawable who);//当drawable需要重绘时执行,这个点的drawable将不可用(或者最少一部分不可用)
void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when);//drawable通过调用该方法来调度下一帧的动画
void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what);//可以取消之前通过scheduleDrawable方法调度的某一帧
}
三个方法都是drawable的动画的调度方法
再看KeyEvent.Callback
public interface Callback {
boolean onKeyDown(int keyCode, KeyEvent event);
boolean onKeyLongPress(int keyCode, KeyEvent event);
boolean onKeyUp(int keyCode, KeyEvent event);
boolean onKeyMultiple(int keyCode, int count, KeyEvent event);
}
KeyEvent.Callback中的前三个方法我们比较常见,分别是Down,Up,LongPress触摸事件的回调,而onKeyMultiple()方法是在快速多次触发down/up事件的情况下调用
最后AccessibilityEventSource
public interface AccessibilityEventSource {
public void sendAccessibilityEvent(int eventType);
public void sendAccessibilityEventUnchecked(AccessibilityEvent event);
}
这个接口是和辅助功能相关的,在这里我们先不管它
View的绘制过程
view能够精准的展示到手机屏幕上,是需要经过精准的计算和绘制的.其绘制过程会经历三个步骤:Measure,Layout,Draw.整个View的绘制是从ViewRootImpl类的performsTraversals()方法开始的,有兴趣的朋友可以下载源码自己看一下。
Measure
我们先来看measure,和measure相关的有三个方法:
public final void measure(int widthMeasureSpec, int heightMeasureSpec);
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec);
private void setMeasureDimension(int measuredWidth, int measuredHeight);
从函数声明来看,measure()被final修饰符修饰,无法被子类重写;onMeasure()可以被子类重写,我们写的自定义View通过重写该方法重新获取View的测量值,如果不重写的话,获取的是默认的测量值通过setMeasureDimension()方法保存测量值.最后一个方法是保存测量值的
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;//强制计算
...
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);//需要计算
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
...
}
measure()方法里的代码并不算特别多,前面的一些代码是配置获取的代码省略掉,来看最关键的逻辑,如果强制计算或需要计算,将执行onMeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);//获取view的测量模式
int specSize = MeasureSpec.getSize(measureSpec);//获取view的测量大小
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
这里出现了specMode,那我们就简单介绍一下,MeasureSpec类中MeasureSpecMode有三种
public static final int UNSPECIFIED ;//未指定子view的大小,子view可以为任意大小
public static final int EXACTLY;//子view最多是父view指定的大小
public static final int AT_MOST;//父view指定了子view的大小
getDefaultSize方法中根据测量模式,返回不同的size大小,当如果是UNSPECIFIED时,返回的是系统建议的最小宽高;如果是AT_MOST和EXACTLY,则返回的测量大小specSize.
所以measure的整个流程就是首先measure()获取保存的参数,再根据设置来判断是否强制测量或忽略之前的计算结果并重新测量,onMeasure()方法则是实际测量的操作,可以被子类重写,默认实现是根据测量模式获取测量大小或默认最小宽高值并保存.
Layout
public void layout(int l, int t, int r, int b) {
...
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);//判断布局是否发生改变
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {//判断布局是否发生改变或强制重新计算
onLayout(changed, l, t, r, b);
...
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
notifyEnterOrExitForAutoFillIfNeeded(true);
}
}
看来layout()和前面的measure()类似,根据布局是否发生改变或强制重新计算来判断是否执行onLayout()方法,我们继续看onLayout()方法
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
看到是空实现的,那么如果子类没有重写onLayout()方法呢?难道不执行布局了吗?我们往回看,发现在调用onLayout之前有根据是否是光学布局模式来调用setOpticalFrame()和setFrame()
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
Insets parentInsets = mParent instanceof View ?
((View) mParent).getOpticalInsets() : Insets.NONE;
Insets childInsets = getOpticalInsets();
return setFrame(
left + parentInsets.left - childInsets.left,
top + parentInsets.top - childInsets.top,
right + parentInsets.left + childInsets.right,
bottom + parentInsets.top + childInsets.bottom);
}
setOpticalFrame()内部调用的也是setFrame(),我们继续来看setFrame()
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
// Remember our drawn bit
int drawn = mPrivateFlags & PFLAG_DRAWN;
...
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// Invalidate our old position
invalidate(sizeChanged);
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
...
}
return changed;
}
mRenderNode.setLeftTopRightBottom(mLeft,mTop,mRight,mBottom)方法,实质上已经将四个参数保存下来了,记录了view的布局信息.
Draw
在测量和布局之后,我们来看看绘制
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* 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 绘制view内容
* 4. Draw children 绘制子view
* 5. If necessary, draw the fading edges and restore layers 如果有必要的话,绘制渐变边缘和还原图层
* 6. Draw decorations (scrollbars for instance) 绘制装饰(例如滑动条)
*/
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case) 一般情况下省略第2-5步
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 绘制view内容
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children 绘制子view
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;
}
...
}
draw(Canvas canvas)方法比较长,大概120行左右,这里只剪取了其中的关键代码,从源代码上来看,绘制流程已经很清楚了,需要注意的是,绘制本身是调用onDraw()方法,绘制子view是调用dispatchDraw()方法.
单个view的绘制流程就是以上,ViewGroup的话用的比较多,而且种类很多,也比较复杂,之后应该会根据单独的类来一个个分析,比如“LinearLayout的绘制流程”这样的标题