Android 터치 이벤트 의 응용 상세 설명

머리말
지난 편 에 서 는 안 드 로 이 드 터치 사건 의 전달 체 제 를 다 루 었 는데 구체 적 으로 여기 서 볼 수 있 습 니 다안 드 로 이 드 터치 이벤트 전달 메커니즘안 드 로 이 드 에서 터치 이벤트 의 전달 과 배 포 를 알 고 있다 면 어떤 문 제 를 해결 할 수 있 는 지,우리 의 실제 개발 에서 어떻게 응용 할 수 있 는 지 하 는 것 이 중요 하 다.원 리 는 문 제 를 해결 하기 위해 준비 한 것 임 을 알 고 있다.이 글 의 핵심 은 View 의 미끄럼 충돌 을 어떻게 해결 하 는 지 에 관 한 것 입 니 다.이 문 제 는 일상적인 개발 에서 흔히 볼 수 있 습 니 다.예 를 들 어 내부 에 포 함 된 Fragment 보 기 는 좌우 로 미 끄 러 지고 외 부 는 하나의 ScrollView 로 포함 되 며 상하 로 미 끄 러 질 수 있 습 니 다.미끄럼 충돌 처 리 를 하지 않 으 면 외부 미끄럼 방향 과 내부 미끄럼 방향 이 일치 하지 않 습 니 다.
목차
흔히 볼 수 있 는 미끄럼 충돌 장면
미끄럼 충돌 처리 규칙
외부 차단 법
내부 차단 법
작은 매듭
흔히 볼 수 있 는 미끄럼 충돌 장면
흔히 볼 수 있 는 미끄럼 충돌 장면 은 다음 과 같은 세 가지 로 간단하게 나 눌 수 있다.
장면 1:외부 미끄럼 방향 과 내부 미끄럼 방향 이 일치 하지 않 음
장면 2:외부 미끄럼 방향 과 내부 미끄럼 방향 이 일치 합 니 다.
장면 3:위의 두 가지 상황 의 내장
그림:

장면 1 은 주로 ViewPager 와 Fragment 를 결합 하여 구 성 된 페이지 슬라이딩 효 과 를 사용 하 는데 주류 응용 은 거의 이 효 과 를 사용한다.이 효 과 는 좌우 슬라이딩 을 통 해 페이지 를 전환 할 수 있 으 며,각 페이지 내 부 는 ListView 이기 때문에 미끄럼 충돌 이 발생 합 니 다.그러나 ViewPager 내부 에서 이러한 미끄럼 충돌 을 처 리 했 기 때문에 ViewPager 를 사용 할 때 우 리 는 이 문 제 를 주목 할 필요 가 없습니다.ViewPager 를 ScrollView 로 바 꾸 면 스스로 수 동 으로 처리 해 야 합 니 다.그렇지 않 으 면 내외 2 층 이 한 층 만 미 끄 러 질 수 있다 는 것 이다.
장면 2.복잡 한 점 에 대해 내외 두 층 이 모두 같은 방향 에서 미 끄 러 질 수 있 을 때 논리 적 인 문제 가 존재 한다.손가락 이 미 끄 러 지기 시 작 했 을 때 시스템 은 사용자 가 도대체 어느 층 을 미 끄 러 뜨리 려 고 하 는 지 알 수 없 기 때문에 손가락 이 미 끄 러 질 때 문제 가 발생 한다.한 층 만 미 끄 러 지 거나 안팎 두 층 이 미 끄 러 지지 만 렉 이 걸린다.
장면 3 은 장면 1 과 장면 2 두 가지 상황 의 상감 으로 더욱 복잡 해 보인다.예 를 들 어 외부 에 SlideMenu 효과 가 있 고 내부 에 ViewPager 가 있 으 며 ViewPager 의 모든 페이지 에 ListView 가 있 습 니 다.장면 3 미끄럼 충돌 은 복잡 해 보이 지만 단일 한 미끄럼 충돌 의 중첩 이기 때문에 일일이 분해 하면 된다.
미끄럼 충돌 처리 규칙
일반적으로 미끄럼 충돌 이 아무리 복잡 하 더 라 도 정 해진 규칙 이 있 기 때문에 이런 규칙 에 따라 우 리 는 적당 한 방법 을 선택 하여 처리 할 수 있다.
장면 1 에 대해 그 처리 규칙 은 사용자 가 좌우 로 미 끄 러 질 때 외부의 View 가 클릭 이 벤트 를 차단 하고 사용자 가 위아래 로 미 끄 러 질 때 내부 View 가 클릭 이 벤트 를 차단 해 야 한 다 는 것 이다.구체 적 으로 미끄럼 이 수평 미끄럼 인지 수직 미끄럼 인지 에 따라 누가 사건 을 차단 하 는 지 판단 하 는 것 이다.
그림:

쉽게 말 해 수평 방향 과 수직 방향의 거리 차 에 따라 판단 하 는 것 으로 Dx>Dy 라면 수평 으로 미 끄 러 지 는 것 이 고,Dy>Dx 라면 수직 으로 미 끄 러 지 는 것 이다.
장면 2 는 비교적 특수 해서 미끄럼 의 각도,거리 차이 와 속도 차이 에 따라 판단 할 수 없다.이 럴 때 업무 에서 돌파 점 을 찾 아야 한다.예 를 들 어 특정한 상태 에 있 을 때 외부 View 가 사용자 의 미끄럼 에 응답 해 야 하고 다른 상태 에 있 을 때 내부 View 가 View 의 미끄럼 에 응답 해 야 한다.
장면 3 의 경우 그의 미끄럼 규칙 도 더욱 복잡 하 다.장면 2 와 마찬가지 로 업무 에서 돌파 점 을 찾 는 것 이다.
외부 차단 법
외부 차단 법 이란 클릭 이벤트 가 모두 부모 용기 의 차단 처 리 를 거 친 것 을 말 합 니 다.부모 용기 가 이 사건 을 필요 로 하면 차단 합 니 다.이 사건 이 필요 하지 않 으 면 차단 하지 않 습 니 다.그러면 미끄럼 충돌 문 제 를 해결 할 수 있 습 니 다.외부 차단 법 은 부모 용기 의 onInterceptTouchEvent 방법 을 다시 써 서 내부 에서 해당 하 는 차단 을 하면 됩 니 다.위조 코드 는 다음 과 같 습 니 다.

 @Override
 public boolean onInterceptTouchEvent(MotionEvent event) {
 boolean intercepted = false;
 int x = (int) event.getX();
 int y = (int) event.getY();

 switch (event.getAction()) {
 case MotionEvent.ACTION_DOWN: {
  intercepted = false;
  break;
 }
 case MotionEvent.ACTION_MOVE: {
  if (           ) {
  intercepted = true;
  } else {
  intercepted = false;
  }
  break;
 }
 case MotionEvent.ACTION_UP: {
  intercepted = false;
  break;
 }
 default:
  break;
 }
 mLastXIntercept = x;
 mLastYIntercept = y;

 return intercepted;
 }

우선 ACTIONDOWN 이 사건 은 부모 용기 가 false 로 돌아 가 야 합 니 다.그러면 후속 move 와 up 의 사건 이 하위 View 에 전 달 될 수 있 도록 합 니 다.move 이벤트 에 따라 차단 여 부 를 결정 하고 부모 용기 가 차단 되면 true 로 돌아 갑 니 다.그렇지 않 으 면 false 로 돌아 갑 니 다.
ViewPager 와 유사 한 컨트롤 을 사용자 정의 합 니 다.ListView 를 포함 하 는 효 과 를 실현 합 니 다.소스 코드 는 다음 과 같 습 니 다.

public class HorizontalScrollViewEx extends ViewGroup {
 private static final String TAG = "HorizontalScrollViewEx";

 private int mChildrenSize;
 private int mChildWidth;
 private int mChildIndex;

 //            
 private int mLastX = 0;
 private int mLastY = 0;
 //            (onInterceptTouchEvent)
 private int mLastXIntercept = 0;
 private int mLastYIntercept = 0;

 private Scroller mScroller;  //      
 private VelocityTracker mVelocityTracker; //      

 public HorizontalScrollViewEx(Context context) {
 super(context);
 init();
 }

 public HorizontalScrollViewEx(Context context, AttributeSet attrs) {
 super(context, attrs);
 init();
 }

 public HorizontalScrollViewEx(Context context, AttributeSet attrs,
  int defStyle) {
 super(context, attrs, defStyle);
 init();
 }

 private void init() {
 mScroller = new Scroller(getContext());
 mVelocityTracker = VelocityTracker.obtain();
 }

 @Override
 public boolean onInterceptTouchEvent(MotionEvent event) {
 boolean intercepted = false;
 int x = (int) event.getX();
 int y = (int) event.getY();

 switch (event.getAction()) {
 case MotionEvent.ACTION_DOWN: {
  intercepted = false;
  if (!mScroller.isFinished()) {
  mScroller.abortAnimation();
  intercepted = true;
  }
  break;
 }
 case MotionEvent.ACTION_MOVE: {
  int deltaX = x - mLastXIntercept;
  int deltaY = y - mLastYIntercept;
  if (Math.abs(deltaX) > Math.abs(deltaY)) {
  intercepted = true;
  } else {
  intercepted = false;
  }
  break;
 }
 case MotionEvent.ACTION_UP: {
  intercepted = false;
  break;
 }
 default:
  break;
 }

 Log.d(TAG, "intercepted=" + intercepted);
 mLastX = x;
 mLastY = y;
 mLastXIntercept = x;
 mLastYIntercept = y;

 return intercepted;
 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
 mVelocityTracker.addMovement(event);
 int x = (int) event.getX();
 int y = (int) event.getY();
 switch (event.getAction()) {
 case MotionEvent.ACTION_DOWN: {
  if (!mScroller.isFinished()) {
  mScroller.abortAnimation();
  }
  break;
 }
 case MotionEvent.ACTION_MOVE: {
  int deltaX = x - mLastX;
  scrollBy(-deltaX, 0);
  break;
 }
 case MotionEvent.ACTION_UP: {
  int scrollX = getScrollX();
  mVelocityTracker.computeCurrentVelocity(1000);
  float xVelocity = mVelocityTracker.getXVelocity();
  if (Math.abs(xVelocity) >= 50) {
  mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;
  } else {
  mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
  }
  mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
  int dx = mChildIndex * mChildWidth - scrollX;
  smoothScrollBy(dx, 0);
  mVelocityTracker.clear();
  break;
 }
 default:
  break;
 }

 mLastX = x;
 mLastY = y;
 return true;
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 int measuredWidth = 0;
 int measuredHeight = 0;
 final int childCount = getChildCount();
 measureChildren(widthMeasureSpec, heightMeasureSpec);

 int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
 int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
 if (childCount == 0) {
  setMeasuredDimension(0, 0);
 } else if (heightSpecMode == MeasureSpec.AT_MOST) {
  final View childView = getChildAt(0);
  measuredHeight = childView.getMeasuredHeight();
  setMeasuredDimension(widthSpaceSize, childView.getMeasuredHeight());
 } else if (widthSpecMode == MeasureSpec.AT_MOST) {
  final View childView = getChildAt(0);
  measuredWidth = childView.getMeasuredWidth() * childCount;
  setMeasuredDimension(measuredWidth, heightSpaceSize);
 } else {
  final View childView = getChildAt(0);
  measuredWidth = childView.getMeasuredWidth() * childCount;
  measuredHeight = childView.getMeasuredHeight();
  setMeasuredDimension(measuredWidth, measuredHeight);
 }
 }

 @Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
 int childLeft = 0;
 final int childCount = getChildCount();
 mChildrenSize = childCount;

 for (int i = 0; i < childCount; i++) {
  final View childView = getChildAt(i);
  if (childView.getVisibility() != View.GONE) {
  final int childWidth = childView.getMeasuredWidth();
  mChildWidth = childWidth;
  childView.layout(childLeft, 0, childLeft + childWidth,
   childView.getMeasuredHeight());
  childLeft += childWidth;
  }
 }
 }

 private void smoothScrollBy(int dx, int dy) {
 mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
 invalidate();
 }

 @Override
 public void computeScroll() {
 if (mScroller.computeScrollOffset()) {
  scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
  postInvalidate();
 }
 }

 @Override
 protected void onDetachedFromWindow() {
 mVelocityTracker.recycle();
 super.onDetachedFromWindow();
 }
}

이 상황 의 차단 조건 은 부모 용기 가 미 끄 러 지 는 과정 에서 수평 거리 차이 가 수직 거리 보다 크 면 차단 하 는 것 이다.그렇지 않 으 면 차단 하지 않 고 사건 을 계속 전달 하 는 것 이다.
내부 차단 법
내부 차단 법 이란 부모 용기 가 어떠한 사건 도 차단 하지 않 고 모든 사건 이 하위 요소 에 전달 되 는 것 을 말한다.만약 에 하위 요소 가 이 사건 이 필요 하면 바로 소모 되 고 그렇지 않 으 면 부모 용기 에 맡 겨 처리 하 는 것 을 말한다.이런 방법 은 Android 의 사건 배포 체제 와 일치 하지 않 기 때문에 requestDisallow InterceptTouchEvent 방법 에 맞 춰 야 정상적으로 작 동 할 수 있다.사용 하기에 외부 차단 법 보다 복잡 하 다.의사 코드 는 다음 과 같 습 니 다:

 @Override
 public boolean dispatchTouchEvent(MotionEvent event) {
 int x = (int) event.getX();
 int y = (int) event.getY();

 switch (event.getAction()) {
 case MotionEvent.ACTION_DOWN: {
  mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(true);
  break;
 }
 case MotionEvent.ACTION_MOVE: {
  int deltaX = x - mLastX;
  int deltaY = y - mLastY;
  if (           ) {
  mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(false);
  }
  break;
 }
 case MotionEvent.ACTION_UP: {
  break;
 }
 default:
  break;
 }

 mLastX = x;
 mLastY = y;
 return super.dispatchTouchEvent(event);
 }

하위 요소 가 requestDisallowInterceptTouchEvent(false)방법 을 호출 할 때 부모 요 소 는 필요 한 이 벤트 를 계속 차단 할 수 있 습 니 다.
앞 에는 비슷 한 ViewPager 를 사용자 정의 합 니 다.지금 은 ListView 를 다시 쓰 십시오.ListView 라 는 이름 의 ListView 를 사용자 정의 한 다음 내부 차단 법의 템 플 릿 코드 를 수정 하면 됩 니 다.

public class ListViewEx extends ListView {
 private static final String TAG = "ListViewEx";

 private HorizontalScrollViewEx2 mHorizontalScrollViewEx2;

 //            
 private int mLastX = 0;
 private int mLastY = 0;

 public ListViewEx(Context context) {
 super(context);
 }

 public ListViewEx(Context context, AttributeSet attrs) {
 super(context, attrs);
 }

 public ListViewEx(Context context, AttributeSet attrs, int defStyle) {
 super(context, attrs, defStyle);
 }

 public void setHorizontalScrollViewEx2(
  HorizontalScrollViewEx2 horizontalScrollViewEx2) {
 mHorizontalScrollViewEx2 = horizontalScrollViewEx2;
 }

 @Override
 public boolean dispatchTouchEvent(MotionEvent event) {
 int x = (int) event.getX();
 int y = (int) event.getY();

 switch (event.getAction()) {
 case MotionEvent.ACTION_DOWN: {
  mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(true);
  break;
 }
 case MotionEvent.ACTION_MOVE: {
  int deltaX = x - mLastX;
  int deltaY = y - mLastY;
  Log.d(TAG, "dx:" + deltaX + " dy:" + deltaY);
  if (Math.abs(deltaX) > Math.abs(deltaY)) {
  mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(false);
  }
  break;
 }
 case MotionEvent.ACTION_UP: {
  break;
 }
 default:
  break;
 }

 mLastX = x;
 mLastY = y;
 return super.dispatchTouchEvent(event);
 }
}

또한 ListView Ex 를 포함 하 는 외부 레이아웃 을 수정 하고 onInterceptTouchEvent 이벤트 에서 차단 하지 않 습 니 다.

public class HorizontalScrollViewEx2 extends ViewGroup {
 private static final String TAG = "HorizontalScrollViewEx2";

 private int mChildrenSize;
 private int mChildWidth;
 private int mChildIndex;
 //            
 private int mLastX = 0;
 private int mLastY = 0;

 //            (onInterceptTouchEvent)
 private int mLastXIntercept = 0;
 private int mLastYIntercept = 0;

 private Scroller mScroller;
 private VelocityTracker mVelocityTracker;

 public HorizontalScrollViewEx2(Context context) {
 super(context);
 init();
 }

 public HorizontalScrollViewEx2(Context context, AttributeSet attrs) {
 super(context, attrs);
 init();
 }

 public HorizontalScrollViewEx2(Context context, AttributeSet attrs,
  int defStyle) {
 super(context, attrs, defStyle);
 init();
 }

 private void init() {
 mScroller = new Scroller(getContext());
 mVelocityTracker = VelocityTracker.obtain();
 }

 @Override
 public boolean onInterceptTouchEvent(MotionEvent event) {
 int x = (int) event.getX();
 int y = (int) event.getY();
 int action = event.getAction();
 if (action == MotionEvent.ACTION_DOWN) {
  mLastX = x;
  mLastY = y;
  if (!mScroller.isFinished()) {
  mScroller.abortAnimation();
  return true;
  }
  return false;
 } else {
  return true;
 }
 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
 Log.d(TAG, "onTouchEvent action:" + event.getAction());
 mVelocityTracker.addMovement(event);
 int x = (int) event.getX();
 int y = (int) event.getY();
 switch (event.getAction()) {
 case MotionEvent.ACTION_DOWN: {
  if (!mScroller.isFinished()) {
  mScroller.abortAnimation();
  }
  break;
 }
 case MotionEvent.ACTION_MOVE: {
  int deltaX = x - mLastX;
  int deltaY = y - mLastY;
  Log.d(TAG, "move, deltaX:" + deltaX + " deltaY:" + deltaY);
  scrollBy(-deltaX, 0);
  break;
 }
 case MotionEvent.ACTION_UP: {
  int scrollX = getScrollX();
  int scrollToChildIndex = scrollX / mChildWidth;
  Log.d(TAG, "current index:" + scrollToChildIndex);
  mVelocityTracker.computeCurrentVelocity(1000);
  float xVelocity = mVelocityTracker.getXVelocity();
  if (Math.abs(xVelocity) >= 50) {
  mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;
  } else {
  mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
  }
  mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
  int dx = mChildIndex * mChildWidth - scrollX;
  smoothScrollBy(dx, 0);
  mVelocityTracker.clear();
  Log.d(TAG, "index:" + scrollToChildIndex + " dx:" + dx);
  break;
 }
 default:
  break;
 }

 mLastX = x;
 mLastY = y;
 return true;
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 int measuredWidth = 0;
 int measuredHeight = 0;
 final int childCount = getChildCount();
 measureChildren(widthMeasureSpec, heightMeasureSpec);

 int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
 int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
 if (childCount == 0) {
  setMeasuredDimension(0, 0);
 } else if (heightSpecMode == MeasureSpec.AT_MOST) {
  final View childView = getChildAt(0);
  measuredHeight = childView.getMeasuredHeight();
  setMeasuredDimension(widthSpaceSize, childView.getMeasuredHeight());
 } else if (widthSpecMode == MeasureSpec.AT_MOST) {
  final View childView = getChildAt(0);
  measuredWidth = childView.getMeasuredWidth() * childCount;
  setMeasuredDimension(measuredWidth, heightSpaceSize);
 } else {
  final View childView = getChildAt(0);
  measuredWidth = childView.getMeasuredWidth() * childCount;
  measuredHeight = childView.getMeasuredHeight();
  setMeasuredDimension(measuredWidth, measuredHeight);
 }
 }

 @Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
 Log.d(TAG, "width:" + getWidth());
 int childLeft = 0;
 final int childCount = getChildCount();
 mChildrenSize = childCount;

 for (int i = 0; i < childCount; i++) {
  final View childView = getChildAt(i);
  if (childView.getVisibility() != View.GONE) {
  final int childWidth = childView.getMeasuredWidth();
  mChildWidth = childWidth;
  childView.layout(childLeft, 0, childLeft + childWidth,
   childView.getMeasuredHeight());
  childLeft += childWidth;
  }
 }
 }

 private void smoothScrollBy(int dx, int dy) {
 mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
 invalidate();
 }

 @Override
 public void computeScroll() {
 if (mScroller.computeScrollOffset()) {
  scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
  postInvalidate();
 }
 }

 @Override
 protected void onDetachedFromWindow() {
 mVelocityTracker.recycle();
 super.onDetachedFromWindow();
 }
}

이 차단 규칙 도 부모 용기 가 미 끄 러 지 는 과정 에서 수평 거리 차 가 수직 거리 차 와 비교 된다.
작은 매듭
전체적으로 보면 미끄럼 충돌 장면 은 세 가지 로 나 눌 수 있 는데 내외 방향 이 일치 하지 않 고 내외 방향 이 일치 하 며 앞의 두 가지 상황 을 포함한다.어떻게 해결 하 든 아무리 복잡 한 미끄럼 충돌 이 있어 도 분리 할 수 있 습 니 다.일정한 규칙 에 따라 첫 번 째 상황 은 미끄럼 거리 차이,속도 차이 와 각도 차이 에 따라 해결 할 수 있 습 니 다.두 번 째 상황 과 세 번 째 상황 은 업무 상 돌파 점 을 찾 을 수 있 습 니 다.업무 상의 한 상 태 는 응답 이 필요 하고 다른 상태 로 전환 할 때 응답 하지 않 습 니 다.업무 수요 에 따라 해당 하 는 처리 규칙 을 얻어 처리 규칙 이 있 으 면 다음 단계 에 처리 할 수 있다.
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기