Android touch事件传递及View的绘制流程

来源:互联网 发布:电脑时间提醒软件 编辑:程序博客网 时间:2024/03/28 22:53

touch事件传递的三个重要方法

dispatchTouchEvent(MotionEvent ev)方法用于事件的分发,当事件传递给当前View时,会被调用,返回值受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件

 /**     * ViewGroup     * @param ev     * @return     */    public boolean dispatchTouchEvent(MotionEvent ev){        ....//其他处理,在此不管        if (disallowIntercept || !onInterceptTouchEvent(ev)) {              ev.setAction(MotionEvent.ACTION_DOWN);              final int scrolledXInt = (int) scrolledXFloat;              final int scrolledYInt = (int) scrolledYFloat;              final View[] children = mChildren;              final int count = mChildrenCount;              for (int i = count - 1; i >= 0; i--) {                  final View child = children[i];                  if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE                          || child.getAnimation() != null) {                      child.getHitRect(frame);                      if (frame.contains(scrolledXInt, scrolledYInt)) {                          final float xc = scrolledXFloat - child.mLeft;                          final float yc = scrolledYFloat - child.mTop;                          ev.setLocation(xc, yc);                          child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;                          if (child.dispatchTouchEvent(ev))  {                              mMotionTarget = child;                              return true;                          }                      }                  }              }          ...//其他处理,在此不管    }    /**     * View     * @param ev     * @return     */    public boolean dispatchTouchEvent(MotionEvent event) {      if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&              mOnTouchListener.onTouch(this, event)) {          return true;      }      return onTouchEvent(event);  }  

由上面源码可以看出,当点击事件产生后,这个ViewGroup中的dispatchTouchEvent会被调用,如果这个ViewGroup的onInterceptTouchEvent方法返回true,就表示要拦截当前事件,然后把事件交给这个ViewGroup处理,即它的onTouchEvent方法被调用,如果这个ViewGroup的onInterceptTouchEvent方法返回false,就表示它 不拦截当前事件,然后继续传递给它的子元素,直至事件被最终处理。

一般情况下,我们不该在普通View内重写dispatchTouchEvent方法,因为它并不执行分发逻辑。当Touch事件到达View时,我们该做的就是是否在onTouchEvent事件中处理它。

流程分析

这里写图片描述
当一个Touch事件(触摸事件为例)到达根节点,即Acitivty的ViewGroup时,它会依次下发,下发的过程是调用子View(ViewGroup)的dispatchTouchEvent方法实现的。简单来说,就是ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViwGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法。上述例子中的消息下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。在上述例子中如果⑤的dispatchTouchEvent返回结果为true,那么⑥-⑦-③-④将都接收不到本次Touch事件。

事件传递的相关结论:

  1. 同一个事件序列在手指接触到离开的整个过程以down事件开始,中间含有不定的move事件,最后以up事件结束,但有些特殊情况会没有up事件,需具体分析。
  2. 一个事件序列只能被一个View拦截消耗,同一个事件序列中的事件不能分别由两个View同时处理,但可以通过onTouchEvent强行传递给其他View处理。
  3. 某个View一旦决定拦截,同一个事件序列的onInterceptTouchEvent不会再被调用,只会调用一次。
  4. ViewGroup默认不拦截任何事件。因为Android源码中ViewGroup的onInterceptTouchEvent方法默认返回false
  5. View没有onEnterceptTouchEvent方法,一旦接收到点击事件,那么它的onTouchEvent方法就会被调用,
  6. View的onTouchEvent默认都会消耗事件(返回true),除非它是不可点击的(clickable和longClickable同时为false)。
  7. View的enable属性不影响onTouchEvent的默认返回值。
  8. onClick触发的前提是当前View是可点击的,且它收到了down和up的事件
  9. 通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程。

View的绘制流程

View的工作流程主要指measure、layout、draw三大流程,即测量,布局,和绘制,measure是确定View的测量宽高,layout是确定View的最终宽/高和四个顶点的位置,draw是将View绘制到屏幕上。
绘制流程图

measure

measure过程的核心方法: measure() - onMeasure() - setMeasuredDimension()
measure调用onMeasure,onMeasure调用setMeasureDimension,measure,setMeasureDimension是final类型,view的子类不需要重写,onMeasure在view的子类中重写。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {      if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||              widthMeasureSpec != mOldWidthMeasureSpec ||              heightMeasureSpec != mOldHeightMeasureSpec) {          // first clears the measured dimension flag          mPrivateFlags &= ~MEASURED_DIMENSION_SET;          if (ViewDebug.TRACE_HIERARCHY) {              ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);          }          // measure ourselves, this should set the measured dimension flag back          onMeasure(widthMeasureSpec, heightMeasureSpec);          // flag not set, setMeasuredDimension() was not invoked, we raise          // an exception to warn the developer          if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {              throw new IllegalStateException("onMeasure() did not set the"                      + " measured dimension by calling"                      + " setMeasuredDimension()");          }          mPrivateFlags |= LAYOUT_REQUIRED;      }      mOldWidthMeasureSpec = widthMeasureSpec;      mOldHeightMeasureSpec = heightMeasureSpec;  }  

View的measure方法中会去调用View的onMeasure方法,因此只需要看onMeasure的实现即可。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {      setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),              getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  }  

setMeasuredDimension方法会设置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;  }  

MeasureSpec有三种模式分别是UNSPECIFIED, EXACTLY和AT_MOST。
EXACTLY表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
AT_MOST表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
UNSPECIFIED表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。

layout

measure过程确定视图的大小,而layout过程确定视图的位置。loyout是从view的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 = 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;     }  

函数中参数l、t、r、b是指view的左、上、右、底的位置,这几个参数是父视图传入的,而根视图中参数是由performTraversals()方法传入的。

layout中调用了onLayout方法,在view中onLayout方法是一个空函数,他需要其子类实现。

我们来看一下LinearLayout的onlayout实现:

@Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        if (mOrientation == VERTICAL) {            layoutVertical();        } else {            layoutHorizontal();        }    }  

其中的layoutHorizontal()为:

void More ...layoutHorizontal(int left, int top, int right, int bottom) {1579        final boolean isLayoutRtl = isLayoutRtl();1580        final int paddingTop = mPaddingTop;15811582        int childTop;1583        int childLeft;1584        1585        // Where bottom of child should go1586        final int height = bottom - top;1587        int childBottom = height - mPaddingBottom; 1588        1589        // Space available for child1590        int childSpace = height - paddingTop - mPaddingBottom;15911592        final int count = getVirtualChildCount();15931594        final int majorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;1595        final int minorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;15961597        final boolean baselineAligned = mBaselineAligned;15981599        final int[] maxAscent = mMaxAscent;1600        final int[] maxDescent = mMaxDescent;16011602        final int layoutDirection = getLayoutDirection();1603        switch (Gravity.getAbsoluteGravity(majorGravity, layoutDirection)) {1604            case Gravity.RIGHT:1605                // mTotalLength contains the padding already1606                childLeft = mPaddingLeft + right - left - mTotalLength;1607                break;16081609            case Gravity.CENTER_HORIZONTAL:1610                // mTotalLength contains the padding already1611                childLeft = mPaddingLeft + (right - left - mTotalLength) / 2;1612                break;16131614            case Gravity.LEFT:1615            default:1616                childLeft = mPaddingLeft;1617                break;1618        }16191620        int start = 0;1621        int dir = 1;1622        //In case of RTL, start drawing from the last child.1623        if (isLayoutRtl) {1624            start = count - 1;1625            dir = -1;1626        }16271628        for (int i = 0; i < count; i++) {1629            int childIndex = start + dir * i;1630            final View child = getVirtualChildAt(childIndex);16311632            if (child == null) {1633                childLeft += measureNullChild(childIndex);1634            } else if (child.getVisibility() != GONE) {1635                final int childWidth = child.getMeasuredWidth();1636                final int childHeight = child.getMeasuredHeight();1637                int childBaseline = -1;16381639                final LinearLayout.LayoutParams lp =1640                        (LinearLayout.LayoutParams) child.getLayoutParams();16411642                if (baselineAligned && lp.height != LayoutParams.MATCH_PARENT) {1643                    childBaseline = child.getBaseline();1644                }1645                1646                int gravity = lp.gravity;1647                if (gravity < 0) {1648                    gravity = minorGravity;1649                }1650                1651                switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {1652                    case Gravity.TOP:1653                        childTop = paddingTop + lp.topMargin;1654                        if (childBaseline != -1) {1655                            childTop += maxAscent[INDEX_TOP] - childBaseline;1656                        }1657                        break;16581659                    case Gravity.CENTER_VERTICAL:1660                        // Removed support for baseline alignment when layout_gravity or1661                        // gravity == center_vertical. See bug #1038483.1662                        // Keep the code around if we need to re-enable this feature1663                        // if (childBaseline != -1) {1664                        //     // Align baselines vertically only if the child is smaller than us1665                        //     if (childSpace - childHeight > 0) {1666                        //         childTop = paddingTop + (childSpace / 2) - childBaseline;1667                        //     } else {1668                        //         childTop = paddingTop + (childSpace - childHeight) / 2;1669                        //     }1670                        // } else {1671                        childTop = paddingTop + ((childSpace - childHeight) / 2)1672                                + lp.topMargin - lp.bottomMargin;1673                        break;16741675                    case Gravity.BOTTOM:1676                        childTop = childBottom - childHeight - lp.bottomMargin;1677                        if (childBaseline != -1) {1678                            int descent = child.getMeasuredHeight() - childBaseline;1679                            childTop -= (maxDescent[INDEX_BOTTOM] - descent);1680                        }1681                        break;1682                    default:1683                        childTop = paddingTop;1684                        break;1685                }16861687                if (hasDividerBeforeChildAt(childIndex)) {1688                    childLeft += mDividerWidth;1689                }16901691                childLeft += lp.leftMargin;1692                setChildFrame(child, childLeft + getLocationOffset(child), childTop,1693                        childWidth, childHeight);1694                childLeft += childWidth + lp.rightMargin +1695                        getNextLocationOffset(child);16961697                i += getChildrenSkipCount(child, childIndex);1698            }1699        }1700    }17011702    private void More ...setChildFrame(View child, int left, int top, int width, int height) {        1703        child.layout(left, top, left + width, top + height);1704    }

注意到上面代码中末尾的setChildFrame方法,可以看出,layout是一个自上而下的过程,先设置父视图位置,在循环子视图,父视图位置一定程度上决定了子视图位置。

draw

  1. 所有视图最终都是调用View的draw方法进行绘制。 在自定义视图中, 也不应该复写该方法, 而是复写onDraw()方法进行绘制, 如果自定义的视图确实要复写该方法,先调用super.draw()完成系统的绘制,再进行自定义的绘制。

  2. onDraw()方法默认是空实现,自定义绘制过程需要复写方法,绘制自身的内容。

  3. dispatchDraw()发起对子视图的绘制,在View中默认为空实现,ViewGroup复写了dispatchDraw()来对其子视图进行绘制。自定义的ViewGroup不应该对dispatchDraw()进行复写。

drow方法有六个步骤:

/*          * 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)          */  

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) {             final Drawable background = mBackground;             if (background != null) {                 final int scrollX = mScrollX;                 final int scrollY = mScrollY;                 if (mBackgroundSizeChanged) {                     background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);                     mBackgroundSizeChanged = false;                 }                 if ((scrollX | scrollY) == 0) {                     background.draw(canvas);                 } else {                     canvas.translate(scrollX, scrollY);                     background.draw(canvas);                     canvas.translate(-scrollX, -scrollY);                 }             }         }         // 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);             // 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);             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);             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);             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);             canvas.drawRect(right - length, top, right, bottom, p);         }         canvas.restoreToCount(saveCount);         // Step 6, draw decorations (scrollbars)         onDrawScrollBars(canvas);     }  

可以看到,第一步是从第20行代码开始的,这一步的作用是对视图的背景进行绘制。这里会先得到一个mBGDrawable对象,然后根据layout过程确定的视图位置来设置背景的绘制区域,之后再调用Drawable的draw()方法来完成背景的绘制工作。那么这个mBGDrawable对象是从哪里来的呢?其实就是在XML中通过android:background属性设置的图片或颜色。当然你也可以在代码中通过setBackgroundColor()、setBackgroundResource()等方法进行赋值。

接下来的第三步是在第150行执行的,这一步的作用是对视图的内容进行绘制。可以看到,这里去调用了一下onDraw()方法,那么onDraw()方法里又写了什么代码呢?进去一看你会发现,原来又是个空方法啊。其实也可以理解,因为每个视图的内容部分肯定都是各不相同的,这部分的功能交给子类来去实现也是理所当然的。

第三步完成之后紧接着会执行第四步,这一步的作用是对当前视图的所有子视图进行绘制。但如果当前的视图没有子视图,那么也就不需要进行绘制了。因此你会发现View中的dispatchDraw()方法又是一个空方法,而ViewGroup的dispatchDraw()方法中就会有具体的绘制代码。

以上都执行完后就会进入到第六步,也是最后一步,这一步的作用是对视图的滚动条进行绘制。那么你可能会奇怪,当前的视图又不一定是ListView或者ScrollView,为什么要绘制滚动条呢?其实不管是Button也好,TextView也好,任何一个视图都是有滚动条的,只是一般情况下我们都没有让它显示出来而已。绘制滚动条的代码逻辑也比较复杂,这里就不再贴出来了,因为我们的重点是第三步过程。

通过以上流程分析,相信大家已经知道,View是不会帮我们绘制内容部分的,因此需要每个视图根据想要展示的内容来自行绘制。如果你去观察TextView、ImageView等类的源码,你会发现它们都有重写onDraw()这个方法,并且在里面执行了相当不少的绘制逻辑。绘制的方式主要是借助Canvas这个类,它会作为参数传入到onDraw()方法中,供给每个视图使用。Canvas这个类的用法非常丰富,基本可以把它当成一块画布。

0 0
原创粉丝点击