2011. 11. 24. 02:02
안드로이드 터치 이벤트 처리 관련
2011. 11. 24. 02:02 in 3. Implementation/Android
참고: http://ecogeo.tistory.com/251
ViewRoot.java 에 보면 deliverPointerEvent 라는 함수가 있습니다. (비슷한 종류로 deliverKeyEvent, deliverKeyEventToViewHierarchy, deliverTrackballEvent 등이 더 있습니다.)
주의! 이 글에서 인용된 코드는 안드로이드 최신 소스가 아닐 수 있습니다.
View nearest = FocusFinder.getInstance().findNearestTouchable 에 보면 적절한 view 를 선택하고 있습니다. 해당 코드는 아래와 같습니다. (FocusFinder.java)
위의 코드를 살펴 보면 ArrayList<View> touchables = root.getTouchables();
라는 부분이 있습니다. 다시 getTouchables 의 소스를 살펴보겠습니다.
아래는View.java 에서 발췌하였습니다.
addTouchables 는 ViewGroup.java 에서 다음과 같이 재정의되어 있습니다.
위 내용에서 터치 대상이 되는 Target View 를 찾는 과정은 지금 진행하고 있는 일에서 꽤 중요한 정보가 될 것 같습니다.
ViewRoot.java 에 보면 deliverPointerEvent 라는 함수가 있습니다. (비슷한 종류로 deliverKeyEvent, deliverKeyEventToViewHierarchy, deliverTrackballEvent 등이 더 있습니다.)
주의! 이 글에서 인용된 코드는 안드로이드 최신 소스가 아닐 수 있습니다.
private void deliverPointerEvent(MotionEvent event) { if (mTranslator != null) { mTranslator.translateEventInScreenToAppWindow(event); } boolean handled; if (mView != null && mAdded) { // enter touch mode on the down boolean isDown = event.getAction() == MotionEvent.ACTION_DOWN; if (isDown) { ensureTouchMode(true); } if(Config.LOGV) { captureMotionLog("captureDispatchPointer", event); } if (mCurScrollY != 0) { event.offsetLocation(0, mCurScrollY); } if (MEASURE_LATENCY) { lt.sample("A Dispatching TouchEvents", System.nanoTime() - event.getEventTimeNano()); } handled = mView.dispatchTouchEvent(event); if (MEASURE_LATENCY) { lt.sample("B Dispatched TouchEvents ", System.nanoTime() - event.getEventTimeNano()); } if (!handled && isDown) { int edgeSlop = mViewConfiguration.getScaledEdgeSlop(); final int edgeFlags = event.getEdgeFlags(); int direction = View.FOCUS_UP; int x = (int)event.getX(); int y = (int)event.getY(); final int[] deltas = new int[2]; if ((edgeFlags & MotionEvent.EDGE_TOP) != 0) { direction = View.FOCUS_DOWN; if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) { deltas[0] = edgeSlop; x += edgeSlop; } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) { deltas[0] = -edgeSlop; x -= edgeSlop; } } else if ((edgeFlags & MotionEvent.EDGE_BOTTOM) != 0) { direction = View.FOCUS_UP; if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) { deltas[0] = edgeSlop; x += edgeSlop; } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) { deltas[0] = -edgeSlop; x -= edgeSlop; } } else if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) { direction = View.FOCUS_RIGHT; } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) { direction = View.FOCUS_LEFT; } if (edgeFlags != 0 && mView instanceof ViewGroup) { View nearest = FocusFinder.getInstance().findNearestTouchable( ((ViewGroup) mView), x, y, direction, deltas); if (nearest != null) { event.offsetLocation(deltas[0], deltas[1]); event.setEdgeFlags(0); mView.dispatchTouchEvent(event); } } } } }
View nearest = FocusFinder.getInstance().findNearestTouchable 에 보면 적절한 view 를 선택하고 있습니다. 해당 코드는 아래와 같습니다. (FocusFinder.java)
public View findNearestTouchable(ViewGroup root, int x, int y, int direction, int[] deltas) { ArrayList<View> touchables = root.getTouchables(); int minDistance = Integer.MAX_VALUE; View closest = null; int numTouchables = touchables.size(); int edgeSlop = ViewConfiguration.get(root.mContext).getScaledEdgeSlop(); Rect closestBounds = new Rect(); Rect touchableBounds = mOtherRect; for (int i = 0; i < numTouchables; i++) { View touchable = touchables.get(i); // get visible bounds of other view in same coordinate system touchable.getDrawingRect(touchableBounds); root.offsetRectBetweenParentAndChild(touchable, touchableBounds, true, true); if (!isTouchCandidate(x, y, touchableBounds, direction)) { continue; } int distance = Integer.MAX_VALUE; switch (direction) { case View.FOCUS_LEFT: distance = x - touchableBounds.right + 1; break; case View.FOCUS_RIGHT: distance = touchableBounds.left; break; case View.FOCUS_UP: distance = y - touchableBounds.bottom + 1; break; case View.FOCUS_DOWN: distance = touchableBounds.top; break; } if (distance < edgeSlop) { // Give preference to innermost views if (closest == null || closestBounds.contains(touchableBounds) || (!touchableBounds.contains(closestBounds) && distance < minDistance)) { minDistance = distance; closest = touchable; closestBounds.set(touchableBounds); switch (direction) { case View.FOCUS_LEFT: deltas[0] = -distance; break; case View.FOCUS_RIGHT: deltas[0] = distance; break; case View.FOCUS_UP: deltas[1] = -distance; break; case View.FOCUS_DOWN: deltas[1] = distance; break; } } } } return closest; }
위의 코드를 살펴 보면 ArrayList<View> touchables = root.getTouchables();
라는 부분이 있습니다. 다시 getTouchables 의 소스를 살펴보겠습니다.
아래는View.java 에서 발췌하였습니다.
/** * Find and return all touchable views that are descendants of this view, * possibly including this view if it is touchable itself. * * @return A list of touchable views */ public ArrayList<View> getTouchables() { ArrayList<View> result = new ArrayList<View>(); addTouchables(result); return result; } /** * Add any touchable views that are descendants of this view (possibly * including this view if it is touchable itself) to views. * * @param views Touchable views found so far */ public void addTouchables(ArrayList<View> views) { final int viewFlags = mViewFlags; if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) && (viewFlags & ENABLED_MASK) == ENABLED) { views.add(this); } }
addTouchables 는 ViewGroup.java 에서 다음과 같이 재정의되어 있습니다.
@Override public void addTouchables(ArrayList<View> views) { super.addTouchables(views); final int count = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < count; i++) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { child.addTouchables(views); } } }
위 내용에서 터치 대상이 되는 Target View 를 찾는 과정은 지금 진행하고 있는 일에서 꽤 중요한 정보가 될 것 같습니다.