View的基础
View的位置参数
View的四个属性:top、left、right、bottom,其中 left是左上角横坐标,top是左上角纵坐标,right是右下角横坐标,bottom 是右下角纵坐标。这些坐标都是相对于 View的父容器;View 的width、height: width=right-left , height=bottom-top ;从 Android3.0开始,View 增加了几个额外的几个参数:x、y、translationX、translationY,其中translationX、translationY 是 View左上角相对父容器的偏移量,默认值为0,View在平移时top、left、right、bottom不会改变,改变的是x、y、translationX、translationY,特别地x=left+translationX , y=top+translationY ;TouchSlop:TouchSlop是系统能识别出的被认为是滑动的最小距离,通过 ViewConfiguration.get(Context).getScaledTouchSlop()。速度追踪,VelocityTracker:
VelocityTracker mVelocityTracker=VelocityTracker.obtain();
mVelocityTracker.addMovement(event)
mVelocityTracker.computeCurrentVelocity(
1000);
int xVelocity=(
int)mVelocityTracker.getXVelocity();
int yVelocity=(
int)mVelocityTracker.getYVelocity();
mVelocityTracker.clear();
mVelocityTracker.recycle();
手势检测:GestureDetector;Scroller的用法:
Scroller scroller =
new Scroller(Context);
private void smoothScrollTo(
int destX ){
int currentX = getScroolX() ;
int delta=destX - currentX;
mScrool.startScrool(currentX,
0,delta,
0,
600);
invalidate();
}
@override
public void computeScroll(){
if(mScrool.computeScroolOffset()){
scrollTo(mScrool.getCurrentX(),mScroll.getCurrentY());
postInvalidate();
}
}
Activity 界面的组成
DecorView作为顶级View,一般情况下它有上下两部分组成(具体情况会和api版本以及Theme有关),上面是title,下面是content,在Activity中我们调用setContentView所设置的view其实就是被加到content中,而如何得到content呢,可以这样:ViewGroup content= (ViewGroup) findViewById (android.R.id.content) ,如何得到我们所设置的view呢,可以这样:content.getChildAt(0)。其实DecorView其实是一个FrameLayout。重要的是View层的大部分事件都是从DecorView传递到我们的view中的。 View的绘制流程是从 ViewRoot的 performTraversals方法开始的,它经过 measure、layout、draw三个过程。而 ViewRoot 是连接 WindowManager和 DecorView 的纽带。
View 的测量
关于 View的测量,我们先来看一看MeasureSpec,从名称上来它可以称为”测量说明”,由此可见,它肯定与 View 的测量有关。
public static class MeasureSpec {
private static final int MODE_SHIFT =
30;
private static final int MODE_MASK =
0x3 << MODE_SHIFT;
public static final int UNSPECIFIED =
0 << MODE_SHIFT;
public static final int EXACTLY =
1 << MODE_SHIFT;
public static final int AT_MOST =
2 << MODE_SHIFT;
public static int makeMeasureSpec(@
IntRange(from =
0, to = (
1 << MeasureSpec.MODE_SHIFT) -
1)
int size,
int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
}
else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
public static int makeSafeMeasureSpec(
int size,
int mode) {
if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
return 0;
}
return makeMeasureSpec(size, mode);
}
public static int getMode(
int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(
int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
由此可见,MeasureSpec是测量的一个帮助类,它用一个32位的 int值封装了一个测量模式以及一个测量大小。高2位代表测量模式,剩下的30位代表测量大小。测量模式有以下几种: 1. UNSPECIFIED:父容器对 View的大小没有任何约束; 2. EXACTLY : View有一个确定的大小; 3. AT_MOST :父容器限定 View 的最大大小;
对于 DecorView ,其 MeasureSpec 由窗口的尺寸和其自身的LayoutParams 来共同确定,而对于应用层 View ,其 MeasureSpec 由父容器的 MeasureSpec 和自身的 LayoutParams 来共同决定MeasureSpec 一旦确定后,其实都是与父容器有关。所有先来看看 ViewGroup 的measureChild(…)方法,另外还有measureChildren(…)以及measureChildWithMargins(…),原理都大同小异;
ViewGroup.measureChild()
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);
}
其实就是根据父容器的MeasureSpec以及子View的LayoutParams得到子View的MeasureSpec,然后调用子View的 measure()方法,来看看是如何得到子View的MeasureSpec;
ViewGroup.getChildMeasureSpec()
public static int getChildMeasureSpec(
int spec,
int padding,
int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(
0, specSize - padding);
int resultSize =
0;
int resultMode =
0;
switch (specMode) {
case MeasureSpec.EXACTLY:
if (childDimension >=
0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
}
else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
}
else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.AT_MOST:
if (childDimension >=
0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
}
else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.UNSPECIFIED:
if (childDimension >=
0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
}
else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ?
0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ?
0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
以下是对应关系:
ViewGroup/ViewEXACTLYAT_MOSTUNSPECIFIED
dp/pxchildDimension(EXACTLY)childDimension(EXACTLY)childDimension(EXACTLY)MATCH_PARENTparentSize(EXACTLY)parentSize(AT_MOST)parentSize(UNSPECIFIED)WRAP_CONTENTparentSize(AT_MOST)parentSize(AT_MOST)parentSize(UNSPECIFIED)
这里的childDimension是 View的LayoutParams中的值,而parentSize指的是父容器的剩余空间; 可见,如果自定义 View时设置布局为WRAP_CONTENT,那么与MATCH_PARENT没有区别,所以要特殊处理MATCH_PARENT的情况,即 AT_MOST 模式。接下来看看 View的measure()方法
View.measure()
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) {
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -
1 : mMeasureCache.indexOfKey(key);
if (cacheIndex <
0 || sIgnoreMeasureCache) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
else {
long value = mMeasureCache.valueAt(cacheIndex);
setMeasuredDimensionRaw((
int) (value >>
32), (
int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
}
View.onMeasure()
protected void onMeasure(
int widthMeasureSpec,
int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
setMeasuredDimension()其实就是将测量得到的 size 设置给mMeasuredWidth和mMeasuredHeight。先看看getSuggestedMinimumWidth(),getSuggestedMinimumHeight()类似。
View.getSuggestedMinimumWidth()
protected int getSuggestedMinimumWidth() {
return (mBackground ==
null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
如果没有设置背景则直接返回mMinWidth,通过minWidth设置的,如是设置了背景了,则取背景的最小值与View设置的最小值二者中的最大者,mBackground是个Drawable对象,普通的图片的宽高就是原图的尺寸,xml 定义的shape size为0。接着看看getDefaultSize();
View.getDefaultSize()
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;
}
如是 Mode为AT_MOST或EXACTLY,直接返回来经父容器调整的specSize,否则返回最小宽高。
View的Layout
onLayout方法是用来确定 View的位置的,View 的 onLayout 是个空实现,因为对于单个的View来说,它的位置是由它的父容器决定的,所以接着看 ViewGroup 的 onLayout 方法,ViewGroup的 onLayout 是个抽象方法,因为每个 ViewGroup 的布局方式都不同,所以 ViewGroup 要求子类实现该方法,定义自己的布局方式。我们可以看看LinearLayout的实现。
LinearLayout.onLayout()
@Override
protected void onLayout(
boolean changed,
int l,
int t,
int r,
int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
}
else {
layoutHorizontal(l, t, r, b);
}
}
我们看下layoutVertical()。
LinearLayout.layoutVertical()
void layoutVertical(
int left,
int top,
int right,
int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
final int width = right - left;
int childRight = width - mPaddingRight;
int childSpace = width - paddingLeft - mPaddingRight;
final int count = getVirtualChildCount();
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
switch (majorGravity) {
case Gravity.BOTTOM:
childTop = mPaddingTop + bottom - top - mTotalLength;
break;
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (bottom - top - mTotalLength) /
2;
break;
case Gravity.TOP:
default:
childTop = mPaddingTop;
break;
}
for (
int i =
0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child ==
null) {
childTop += measureNullChild(i);
}
else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity <
0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) /
2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
代码逻辑还是比较清晰的,首先通过LinearLayout 自身的Gravity来确定第一个子 View的起点纵坐标,这里只会解析BOTTOM、CENTER_VERTICAL、TOP三种Gravity,接着逐一对子 View 进行操作。先根据子 View 的 Gravity 来确定子Veiw的的横坐标,之后就是垂直方向的纵坐标叠加了。这时最后调用了setChildFrame(),其实就是调用了下 子 Veiw的 layout()方法;
View.layout()
public void layout(
int l,
int t,
int r,
int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) !=
0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
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_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li !=
null && li.mOnLayoutChangeListeners !=
null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (
int i =
0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(
this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
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;
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) !=
0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) !=
0;
if (!verticalEdges && !horizontalEdges) {
if (!dirtyOpaque) onDraw(canvas);
dispatchDraw(canvas);
if (mOverlay !=
null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
onDrawForeground(canvas);
return;
}
boolean drawTop =
false;
boolean drawBottom =
false;
boolean drawLeft =
false;
boolean drawRight =
false;
float topFadeStrength =
0.0f;
float bottomFadeStrength =
0.0f;
float leftFadeStrength =
0.0f;
float rightFadeStrength =
0.0f;
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);
if (offsetRequired) {
right += getRightPaddingOffset();
bottom += getBottomPaddingOffset();
}
final ScrollabilityCache scrollabilityCache = mScrollCache;
final float fadeHeight = scrollabilityCache.fadingEdgeLength;
int length = (
int) fadeHeight;
if (verticalEdges && (top + length > bottom - length)) {
length = (bottom - top) /
2;
}
if (horizontalEdges && (left + length > right - length)) {
length = (right - left) /
2;
}
if (verticalEdges) {
topFadeStrength = Math.max(
0.0f, Math.min(
1.0f, getTopFadingEdgeStrength()));
drawTop = topFadeStrength * fadeHeight >
1.0f;
bottomFadeStrength = Math.max(
0.0f, Math.min(
1.0f, getBottomFadingEdgeStrength()));
drawBottom = bottomFadeStrength * fadeHeight >
1.0f;
}
if (horizontalEdges) {
leftFadeStrength = Math.max(
0.0f, Math.min(
1.0f, getLeftFadingEdgeStrength()));
drawLeft = leftFadeStrength * fadeHeight >
1.0f;
rightFadeStrength = Math.max(
0.0f, Math.min(
1.0f, getRightFadingEdgeStrength()));
drawRight = rightFadeStrength * fadeHeight >
1.0f;
}
saveCount = canvas.getSaveCount();
int solidColor = getSolidColor();
if (solidColor ==
0) {
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
if (drawTop) {
canvas.saveLayer(left, top, right, top + length,
null, flags);
}
if (drawBottom) {
canvas.saveLayer(left, bottom - length, right, bottom,
null, flags);
}
if (drawLeft) {
canvas.saveLayer(left, top, left + length, bottom,
null, flags);
}
if (drawRight) {
canvas.saveLayer(right - length, top, right, bottom,
null, flags);
}
}
else {
scrollabilityCache.setFadeColor(solidColor);
}
if (!dirtyOpaque) onDraw(canvas);
dispatchDraw(canvas);
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
if (drawTop) {
matrix.setScale(
1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, right, top + length, p);
}
if (drawBottom) {
matrix.setScale(
1, fadeHeight * bottomFadeStrength);
matrix.postRotate(
180);
matrix.postTranslate(left, bottom);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, bottom - length, right, bottom, p);
}
if (drawLeft) {
matrix.setScale(
1, fadeHeight * leftFadeStrength);
matrix.postRotate(-
90);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, left + length, bottom, p);
}
if (drawRight) {
matrix.setScale(
1, fadeHeight * rightFadeStrength);
matrix.postRotate(
90);
matrix.postTranslate(right, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(right - length, top, right, bottom, p);
}
canvas.restoreToCount(saveCount);
if (mOverlay !=
null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
onDrawForeground(canvas);
}
draw 的方法比较简单,注释已经讲的很清楚了。不过,在 View中有个setWillNotDraw方法:
public void setWillNotDraw(
boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW :
0, DRAW_MASK);
}
可以在不需要绘制 View的时候设置这个为 true,这样能进行相应有优化。