View 体系
来源:互联网 发布:今天eia数据 编辑:程序博客网 时间:2024/05/17 08:12
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();//x 方向速度int yVelocity=(int)mVelocityTracker.getYVelocity();//y方向速度mVelocityTracker.clear();//清除mVelocityTracker.recycle();//回收
- 手势检测:GestureDetector;
- Scroller的用法:
Scroller scroller = new Scroller(Context);//自定义方法private void smoothScrollTo(int destX ){ int currentX = getScroolX() ; int delta=destX - currentX; //600ms内滑到 dsetX mScrool.startScrool(currentX,0,delta,0,600); invalidate();}@overridepublic 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); //父ViewGroup的大小 int specSize = MeasureSpec.getSize(spec); //父ViewGroup的剩余大小(除去父ViewGroup的 padding 以及子 View的 margin) 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); }
以下是对应关系:
这里的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) { // first clears the measured dimension flag 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); // 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; } //*****************省略部分代码******************* 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; // Where right end of child should go final int width = right - left; int childRight = width - mPaddingRight; // Space available for child 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; //LinearLayout 的Gravity switch (majorGravity) { case Gravity.BOTTOM: // mTotalLength contains the padding already childTop = mPaddingTop + bottom - top - mTotalLength; break; // mTotalLength contains the padding already 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); } //如果View的可见状态不是 GONE 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; //这里调用了子 View 的layout方法 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方法 onLayout(changed, l, t, r, b); mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; //如果布局改变了,遍历调用OnLayoutChangeListener的onLayoutChange方法 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; /* * 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 * 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) 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); // 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); // we're done... return; } /* * Here we do the full fledged routine... * (this is an uncommon case where speed matters less, * this is why we repeat some of the tests that have been * done above) */ 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; // Step 2, save the canvas' layers 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; // clip the fade length if top and bottom fades overlap // overlapping fades produce odd-looking artifacts if (verticalEdges && (top + length > bottom - length)) { length = (bottom - top) / 2; } // also clip horizontal fades if necessary 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); } // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 5, draw the fade effect and restore layers 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); // 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); }
draw 的方法比较简单,注释已经讲的很清楚了。不过,在 View中有个setWillNotDraw方法:
public void setWillNotDraw(boolean willNotDraw) { setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); }
可以在不需要绘制 View的时候设置这个为 true,这样能进行相应有优化。
- View体系
- View 体系
- View的事件体系
- View 的事件体系
- View的事件体系
- View的事件体系
- View的事件体系
- View的事件体系
- View的事件体系
- View的事件体系
- View的事件体系
- View的事件体系
- View的事件体系
- View的事件体系
- View的事件体系
- view 事件体系
- View滑动体系
- View的事件体系
- 第二行代码学习笔记——第七章:跨程序共享数据——探究内容提供器
- cocoapods快速安装法
- #bzoj1084#草地排水(网络流)
- CentOS zookeeper dockerfile
- Win10系统打开共享文件提示没有权限使用网络资源怎么处理?
- View 体系
- 最简单的几何着色器(Geometry Shader)【OpenGL】【GLSL】
- A/D采集模块总结
- 【随时加】广告
- SQL语句分类和差别
- 1451: 幸运数字
- Android捕捉图像后在SurfaceView上变形显示问题的处理
- hibernate的基本使用
- 正则表达式