Android源码学习之View

2014-12-20

Android源码学习之View

在Android中,View是所有视图控件的基类。Android的视图控件的设计采用了经典的设计模式--组合模式。View是基础控件,而ViewGroup是可以包含子View和管理子View的空间,ViewGroup同时也是一个View。这跟iOS的视图空间设计有很大不同。在iOS的视图框架中,所有视图都是继承UIView,而UIView本身是可以包含和管理子UIView的。我不知道这两种设计有什么优劣,感觉iOS的设计更简单直接。

View的源代码超过2万行,当然包括了很多注释。这里面定义了很多基本的方法,主要是视图渲染的相关方法和事件的相关方法,不过有一点我不是很明白的就是里面有不少是根scrollbar相关的方法。我们实际应用中貌似有scrollbar的控件貌似只有AdapterView的子控件,ScrollView和TextView,scrollbar的特性为什么不直接放到这些类中,或者专门为这些类专门建立一个父类来处理scrollbar。View中的scrollbar特性应该大部分视图控件都不需要吧。当然这些只是我的疑问罢了,Google这样设计也许有他的道理。

重要方法

一个View要渲染到屏幕上,有几个重要的方法:

  • onMeasure系方法
  • onLayout系方法
  • onDraw系方法

onMeasure系方法

每一个onXXX方法又有若干个相关XXX方法。例如onMeasure方法,相关的方法是measure(int widthMeasureSpec, int heightMeasureSpec)方法,setMeasuredDimension(int measuredWidth, int measuredHeight)方法,MeasureSpec内部类。其中measure(int widthMeasureSpec, int heightMeasureSpec)是final方法,由系统调用,这个方法会调用onMeasure方法,这个方法需要子类覆盖实现来计算本身的大小,而且覆盖这个方法的时候必须调用setMeasuredDimension方法。这里跟计算大小相关密切的是MeasureSpec内部类,这里要结合ViewGroup类讲解才比较清楚。View中这个相关方法的实现是比较简单的,更多定义一个框架,需要子类自己定制实现。我之前总是搞不太清这些方法的关系,看这篇 ANDROID自定义视图——onMeasure,MeasureSpec源码 流程 思路详解才比较清楚。这个我在ViewGroup学习中再讲。简单来讲View通过onMeasure来确定自己的大小。

onLayout系方法

onLayout(boolean changed, int left, int top, int right, int bottom)方法在View中是一个空实现,子类需要自己覆盖。跟这个类密切相关的方法是layout(int l, int t, int r, int b)方法,这个给外部调用的,一般是父控件来给它设定位置。还有setFrame(int left, int top, int right, int bottom) 和 onSizeChanged(int w, int h, int oldw, int oldh)方法。onSizeChanged(int w, int h, int oldw, int oldh)方法也是一个空方法。onLayout方法我用的比较少,一般貌似在需要动态滚动视图时用这个方法比较多。以后加深学习这个方法。

onDraw系方法

onDraw方法也是一个空实现。一般来说这个方法用得比较多,大家都知道要自定义View就需要重载这个方法。一开始我很奇怪,既然View的onDraw方法是空实现,而我经常用View来做一些线控件,即给View设置一个背景颜色,宽或者高设置为1像素。那它的背景是怎么画上去的?答案是在draw(Canvas canvas)方法中画的。onDraw系方法还有draw(Canvas canvas, ViewGroup parent, long drawingTime)方法,dispatchDraw(Canvas canvas)方法。dispatchDraw(Canvas canvas)方式是空实现,而draw(Canvas canvas, ViewGroup parent, long drawingTime)的实现相当复杂,这两个方法都是由ViewGroup来调用的。真正的重头戏在draw(Canvas canvas)方法中。

 /**
 * Manually render this view (and all of its children) to the   given Canvas.
 * The view must have already done a full layout before this function is
 * called.  When implementing a view, implement
 * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
 * If you do need to override this method, call the superclass version.
 *
 * @param canvas The Canvas to which the View is rendered.
 */

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);

        // Step 6, draw decorations (scrollbars)
        onDrawScrollBars(canvas);

        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(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);

    // Step 6, draw decorations (scrollbars)
    onDrawScrollBars(canvas);

    if (mOverlay != null && !mOverlay.isEmpty()) {
        mOverlay.getOverlayView().dispatchDraw(canvas);
    }
}

这个方法中说得很清楚,View的渲染分6步,也可以省略第2和第5步。

第一步,画背景
第二步,可能的话(一般是要做动画),把canvas保存起来
第三步,画内容
第四步,画子控件
第五步,还原第二部保存的canvas
第六步,画装饰(scrollbar

这样就把整个视图画出来了。

Touch事件

在View中有很大部分是处理事件,包括按键事件,滚动球事件,触摸事件。这里主要讲触摸事件,因为这个用得最多。 这里主要有onTouch方法,dispatchTouch方法。dispatchTouch是个重要的方法,我们的触摸事件都是经过dispatchTouch分发下去的,但是View是子控件它的分发其实就是发送给自己。我看到源码里有这么一段:

  if (onFilterTouchEventForSecurity(event)) {
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

这里说明如果你重载了一个View的onTouch方法,并且设置了它的OnTouchListener,那么它将调用OnTouchListener的方法,而不会调用本身的onTouch方法。 而onTouch的实现,主要是焦点处理,点击和安装事件的处理,并没有太多特别的东西。我一般考面试者View的touch事件哪些,从屏幕中点击一个按钮,事件是怎么传递的,相关的方法有哪些。这里ViewGroup有一个拦截事件方法(忘了单词怎么打),是View里没有的,又是怎么工作。这里很考面试者对触摸事件的理解,后续我会说。

View还有一些重要的方法,这里就不一一列举,而且View的方法要结合ViewGroup的实现理解,下次写ViewGroup.

Category: Android Tagged: Android源码学习 View

Comments