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事件。
事件传递的相关结论:
- 同一个事件序列在手指接触到离开的整个过程以down事件开始,中间含有不定的move事件,最后以up事件结束,但有些特殊情况会没有up事件,需具体分析。
- 一个事件序列只能被一个View拦截消耗,同一个事件序列中的事件不能分别由两个View同时处理,但可以通过onTouchEvent强行传递给其他View处理。
- 某个View一旦决定拦截,同一个事件序列的onInterceptTouchEvent不会再被调用,只会调用一次。
- ViewGroup默认不拦截任何事件。因为Android源码中ViewGroup的onInterceptTouchEvent方法默认返回false
- View没有onEnterceptTouchEvent方法,一旦接收到点击事件,那么它的onTouchEvent方法就会被调用,
- View的onTouchEvent默认都会消耗事件(返回true),除非它是不可点击的(clickable和longClickable同时为false)。
- View的enable属性不影响onTouchEvent的默认返回值。
- onClick触发的前提是当前View是可点击的,且它收到了down和up的事件
- 通过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
所有视图最终都是调用View的draw方法进行绘制。 在自定义视图中, 也不应该复写该方法, 而是复写onDraw()方法进行绘制, 如果自定义的视图确实要复写该方法,先调用super.draw()完成系统的绘制,再进行自定义的绘制。
onDraw()方法默认是空实现,自定义绘制过程需要复写方法,绘制自身的内容。
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这个类的用法非常丰富,基本可以把它当成一块画布。
- Android touch事件传递及View的绘制流程
- android View 绘制流程 和 事件传递
- android View touch的传递流程
- Android Touch事件传递流程
- Android中view的Touch事件传递顺序
- 深入探索Android 中view的touch事件传递
- Android View touch事件传递方式规律
- Android View Touch事件传递机制
- android View System touch 事件分发流程
- Android的自定义View及View的绘制流程
- View的Touch事件分发流程
- View的Touch事件分发简要流程
- Android View 绘制流程 及 自定义View
- Android View绘制的流程
- Android View的绘制流程
- android View的绘制流程
- Android View的绘制流程
- android--View 的绘制流程
- HDU1069 Monkey and Banana (动态规划)
- 转:ViewPager禁止滑动
- 为什么越来越多的开发者选择使用Spring Boot
- 刚开始使用push遇到的小问题
- tomcat如何进行请求信息编码
- Android touch事件传递及View的绘制流程
- 欢迎使用CSDN-markdown编辑器
- 利用Python数据分析:Numpy基础(四)
- windows JDK环境变量配置
- mybatis的学习
- Hibernate 二级缓存
- JAVASE基础-day09(面向对象)
- Require.js
- C#之MySql新增