View 体系

来源:互联网 发布:今天eia数据 编辑:程序博客网 时间:2024/05/17 08:12

View的基础

View的位置参数

  1. View的四个属性:top、left、right、bottom,其中 left是左上角横坐标,top是左上角纵坐标,right是右下角横坐标,bottom 是右下角纵坐标。这些坐标都是相对于 View的父容器;
  2. View 的width、height: width=right-left , height=bottom-top ;
  3. 从 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 ;
  4. TouchSlop:TouchSlop是系统能识别出的被认为是滑动的最小距离,通过 ViewConfiguration.get(Context).getScaledTouchSlop()。
  5. 速度追踪,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();//回收
  1. 手势检测:GestureDetector;
  2. 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);    }

以下是对应关系:

ViewGroup/View EXACTLY AT_MOST UNSPECIFIED dp/px childDimension(EXACTLY) childDimension(EXACTLY) childDimension(EXACTLY) MATCH_PARENT parentSize(EXACTLY) parentSize(AT_MOST) parentSize(UNSPECIFIED) WRAP_CONTENT parentSize(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) {            // 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,这样能进行相应有优化。

0 0
原创粉丝点击