onDraw和onTouch的坑

2/13/2017来源:iOS开发人气:798

概述

在我做一个小说阅读器的时候,遇到一个很神奇的坑,随后我找了许多资料,也看了些源码,也只是有了一些猜测,这里记录一下,如果有人知道具体的原因烦请不吝赐教。

现象

首先我们看一个现象,我写了代码进行测试

public class MyView extends View { int count = 1; PRivate Scroller scroller; private boolean flag = true; public MyView(Context context) { super(context); scroller = new Scroller(getContext()); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Log.d("Debug", "draw"); if (flag) { flag = false; scroller.startScroll(0, 0, 100, 100, 600); invalidate(); } } @Override public void computeScroll() { super.computeScroll(); if (scroller.computeScrollOffset()) { Log.d("Debug", "compute " + scroller.isFinished()); invalidate(); } } }

输出的log如图 这里写图片描述 第一个draw就是初始化的onDraw这没有问题,加下来成对出现的compute和draw就是调用invalidate后系统的调用,可以看出系统先调用compute再onDraw。最后又多了一个draw,那是怎么回事?可以看出最后一个commpute的时候返回isFinish是true,说明滑动结束,这个compute会发出最后一个invalidate,随后系统又依次调用compute和onDraw,不过compute失败了所以就只有一个draw。

现在说一下当时的情况,当时做的一个翻页的效果,在onTouchEvent()方法中对手指的操作作出反应。当UP或者CANCEL的时候开启自动翻页的动画,使用的Scroller。

onTouchEvent

if (event.getAction() == MotionEvent.ACTION_UP) { if (canDragOver()) { startAnimation(700); } else { // 恢复原样 mTouch.x = mCornerX +0.1f; mTouch.y = mCornerY +0.1f; } } private void startAnimation(int delayMillis) { int dx, dy; if (mCornerX > 0) { dx = -(int) (mWidth + mTouch.x); } else { dx = (int) (mWidth - mTouch.x + mWidth); } if (mCornerY > 0) { dy = (int) (mHeight - mTouch.y) - 1; } else { dy = (int) (1 - mTouch.y); // 防止mTouch.y最终变为0 } mScroller.startScroll((int) mTouch.x, (int) mTouch.y, dx, dy, delayMillis); invalidate(); } public void computeScroll() { super.computeScroll(); if (mScroller.computeScrollOffset()) { float x = mScroller.getCurrX(); float y = mScroller.getCurrY(); mTouch.x = x; mTouch.y = y; //必须重绘调用onDraw方法 postInvalidate(); } }

然后当然是在onDraw中根据mTouch的坐标来绘制了。 接下来在DOWN的时候首先中断动画,然后判断是否有上一页(假设是往前翻),如果没有返回false,不做任何反应,接下来的操作也不接收

onTouchEvent

if (event.getAction() == MotionEvent.ACTION_DOWN) { // 中断动画 abortAnimation(); mTouch.x = event.getRawX(); mTouch.y = event.getRawY(); calcCornerXY(mTouch.x, mTouch.y); // 绘制当前页面 mBookFactory.draw(mCurrentCanvas); int state = -1; //向右 if(DragToRight()) { state = mBookFactory.prePage(); } else { state = mBookFactory.nextPage(); } if (state == BookFactory.STATE_SUCCESS) { mBookFactory.draw(mNextCanvas); } else if (state == BookFactory.STATE_NULL) { if (DragToRight()) { ToastUtil.showShort("当前是第一页哦"); } else { ToastUtil.showShort("最后一页了哦"); } // 直接返回false,不invalidate,不接受接下来的action Log.d("Debug", "return false"); return false; } else { mNextCanvas.drawBitmap(mBookFactory.getmBackgroundBitmap(), 0, 0, null); startAnimation(700); Toast.makeText(getContext(), "正在加载", Toast.LENGTH_SHORT).show(); } }

理论上来说,如果我从第二页翻到第一页的时候,然后第一页再往上翻肯定没有反应的,可是结果却很出乎意料,如图 这里写图片描述 可以看见,我在翻到第一页的时候翻到一半松手,这时候就会掉用startAnimation自动翻页,然而我在还没有自动翻完的时候迅速往右翻,这时候可以翻出一个角,随后不动。可是我在DOWN的时候不是返回false了吗,那就说明不会invalidate(onTouchEvent方法最后我写了一个invalidate刷新页面),可是为什么还会翻出一角? 随后我在DOWN的return false之前打上log,在onDraw方法中也打了log,输出的log如下 这里写图片描述 我们分析一下,在我们return false之后,还有一个draw,这不可能是我们return false之后系统再调用的computeScroll来调用的invalidate,因为在return false之前就abortAnimation了,scroll的finish是true,在computeScroll中的mScroller.computeScrollOffset()返回的是false。所以就是我们上面所说最后一个validate了,在DOWN之前就有一个DRAW事件发出进入消息队列了(详情见android的Handler机制),然后我们DOWN的时候发出一个TOUCH事件进入消息队列,这时候问题又来了,为啥TOUCH后来却比DRAW要早呢?

猜测与验证

这里我只能猜测,是touch事件的优先级比较高,在发送这个事件到消息队列的时候将message.when设置为0,消息队列的结构是链表结构,入队的时候通过比较when来判断位置,设置为0那就是队头,所以优先处理。我带着这样的猜测去查看源码,但是没有找到任何证据来证实我的猜测,后续如果有结果在更新。。。