在我做一個小說閱讀器的時候,遇到一個很神奇的坑,隨后我找了許多資料,也看了些源碼,也只是有了一些猜測,這里記錄一下,如果有人知道具體的原因煩請不吝賜教。
首先我們看一個現象,我寫了代碼進行測試
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那就是隊頭,所以優先處理。我帶著這樣的猜測去查看源碼,但是沒有找到任何證據來證實我的猜測,后續如果有結果在更新。。。
新聞熱點
疑難解答