ViewPager 2 미끄럼 충돌 해결 방안

지난해 12 월 ViewPager 2 정식 판이 발 표 된 이후 ViewPager 2 는 기 존 버 전의 ViewPager 를 점차 대체 하기 시작 했다.많은 개발 자 들 도 이미 프로젝트 에서 ViewPager 2 를 사용 했다.ViewPager 에 비해 ViewPager 2 의 기능 이 강하 지 않다 고 할 수 없습니다.제 가 전에 쓴 글 인 에서 ViewPager 2 의 사용 에 대해 상세 하 게 설명 한 적 이 있 습 니 다.그러나 당시 실전 이 많 지 않 았 기 때문에 뷰 페 거 2 의 끼 워 넣 기 사용 에 심각 한 미끄럼 충돌 이 있 었 던 것 은 발견 되 지 않 았 다.이 문 제 는 올해 3 월 ViewPager 2 로 Banner ViewPager 를 재 구성 할 때 비로소 발견 됐다.이에 따라 BVP 3.0 버 전에 서 뷰 페 거 2 를 추가 로 슬라이딩 충돌 처리 해 그 나 마 효 과 는 미미 한 편 이다.또 포럼 에 서 는 뷰 퍼 거 2 슬라이딩 충돌 에 대한 도움 요청 게시 물 을 적 잖 게 봤 고,뷰 퍼 거 2 슬라이딩 충돌 을 검색 해 배 너 뷰 퍼 거 지 텀 홈 페이지 를 찾 은 친구 들 도 있 었 다.그렇다면 글 을 써 서 미끄럼 충돌 을 처리 한 경험 을 공유 하 는 것 이 좋 겠 습 니 다.ěn)식**(sī)**,헤헤 헤헤.
1.왜 ViewPager 는 미끄럼 충돌 이 없 습 니까?
이 의문 이 있 는 지 모 르 겠 습 니 다.ViewPager 시대 에 ViewPager 내장 ViewPager 는 미끄럼 충돌 이 발생 한 적 이 없습니다.그런데 왜 ViewPager 의 업그레이드 버 전인 ViewPager 2 에서 미끄럼 충돌 이 일 어 났 을까요?이 문 제 를 밝 히 려 면 ViewPager 와 ViewPager 2 의 내부 에 깊이 들 어가 그들의 소스 코드 를 분석 해 야 한다.
미끄럼 충돌 은 onInterceptTouchEvent 방법 에서 처리 해 야 한 다 는 것 을 알 고 있 으 며,자신의 조건 에 따라 사건 을 차단 할 지 여 부 를 결정 합 니 다.ViewPager 의 소스 코드 에서 다음 코드 를 볼 수 있 습 니 다(읽 기 편 하고 코드 가 삭제 되 었 습 니 다).

@Override
 public boolean onInterceptTouchEvent(MotionEvent ev) {

  final int action = ev.getAction() & MotionEvent.ACTION_MASK;
  if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
  	//                 
   resetTouch();
   return false;
  }


  switch (action) {
   case MotionEvent.ACTION_MOVE: {
    //                       2 ,              
    if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) { 
     mIsBeingDragged = true;
     //   Parent View    ,         ViewPager
     requestParentDisallowInterceptTouchEvent(true);
     setScrollState(SCROLL_STATE_DRAGGING);
    } else if (yDiff > mTouchSlop) {
     mIsUnableToDrag = true;
    }
    break;
   }

   case MotionEvent.ACTION_DOWN: {  
    if (mScrollState == SCROLL_STATE_SETTLING
      && Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) {
    		//  Down     Parent View    ,            ViewPager
     requestParentDisallowInterceptTouchEvent(true);
     setScrollState(SCROLL_STATE_DRAGGING);
    } else {
     completeScroll(false);
     mIsBeingDragged = false;
    }
    break;
   }

   case MotionEvent.ACTION_POINTER_UP:
    onSecondaryPointerUp(ev);
    break;
  }
  return mIsBeingDragged;
 }
ACTION 에서 보 실 수 있 습 니 다.DOWN 과 ACTIONMOVE 에 서 는 일부 판단 조건 에 따라 requestParent Disallow Intercept TouchEvent(true)방법 을 호출 하여 Parent View 차단 사건 을 금지 하 였 습 니 다.즉,ViewPager 가 미끄럼 충돌 을 처리 해 주 었 기 때문에 우 리 는 사용 만 하면 됩 니 다.미끄럼 충돌 문 제 를 걱정 하지 않 아 도 됩 니 다.
현재,우 리 는 ViewPager 2 로 이동 하여 원본 코드 를 뒤 져 보 니 RecyclerView 의 실현 클래스 에 만 onInterceptTouchEvent 와 관련 된 방법 이 있 고,이 코드 는 사용자 가 입력 하지 않 은 논 리 를 처리 하 는 것 일 뿐 입 니 다!

private class RecyclerViewImpl extends RecyclerView {

  .... //       

  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
   return isUserInputEnabled() && super.onInterceptTouchEvent(ev);
  }
 }
그 러 니까 ViewPager 2 는 사실 미끄럼 충돌 을 처리 해 주지 않 았 다 는 거 야!왜 그 럴 까요?ViewPager 2 개발 자 들 이 이 일 을 잊 어 버 린 건 가?여 기 는 내 가 장담 하 건 대 틀림없이 이 렇 지 않 을 것 이다.사실 ViewPager 2 의 구 조 를 보면 대충 알 수 있 습 니 다.ViewPager 2 가 final 로 밝 혀 졌 다 는 것 은 ViewPager 2 를 계승 하 는 것 처럼 ViewPager 2 를 수정 할 수 없다 는 것 을 의미한다.만약 에 정부 가 ViewPager 2 내부 에서 미끄럼 충돌 을 자체 적 으로 처리 했다 면 특별한 수요 가 있 으 면 우리 자신의 상황 에 따라 ViewPager 2 의 미끄럼 을 처리 해 야 합 니 다.그러면 공식 적 으로 쓴 미끄럼 충돌 을 처리 하 는 코드 는 우리 자신의 수요 에 영향 을 줄 수 있 습 니까?그래서 저 는 이 때문에 아예 아무런 처리 도 하지 않 고 개발 자 에 게 전권 을 맡 겼 다 고 생각 합 니 다.
2.미끄럼 충돌 처리 방안
정부 가 우리 에 게 처리 하지 않 는 이상 우리 스스로 손 을 써 야 한다.시작 하기 전에 미끄럼 충돌 을 처리 하 는 두 가지 방안 을 알 아 보 겠 습 니 다.미끄럼 충돌 이 발생 한 이상 두 레이아웃 이 서로 겹 쳐 서 일어 난 것 이 분명 하 다.두 개의 포석 인 이상 우 리 는 두 방향 으로 나 누 어 처리 할 수 있다.이른바 외부 차단 법 과 내부 차단 법 이다.
1.외부 차단 법
이른바'외부 차단 법'중의 외 부 는 미끄럼 충돌 이 발생 하 는 이 두 구조의 외층 을 가리킨다.우 리 는 하나의 사건 서열 이 Parent View 에서 먼저 얻 은 것 임 을 알 고 있 습 니 다.만약 Parent View 가 사건 을 차단 하지 않 으 면 하위 View 에 의 해 처 리 됩 니 다.외부 에서 먼저 사건 을 알 게 된 이상 외부 View 는 자신의 상황 에 따라 사건 을 차단 할 지 여 부 를 결정 하면 되 지 않 겠 습 니까?따라서 외부 차단 법의 실현 은 매우 간단 하 다.대략적인 사고방식 은 다음 과 같다.

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 (needIntercept) { //               
     intercepted = true;
    } else {
     intercepted = false;
    }
    break;
   }
   case MotionEvent.ACTION_UP: {
    intercepted = false;
    break;
   }
   default:
    break;
  }
  mLastXIntercept = x;
  mLastYIntercept = y;
  return intercepted;
 }
2.내부 차단 법
이른바'내부 차단 법'이란 내부 뷰 에 대해 글 을 써 서 내부 뷰 로 하여 금 사건 차단 여 부 를 결정 하 게 하 는 것 을 말한다.그런데 지금 문제 가 생 겼 어 요.외부 View 가 사건 을 차단 하려 는 건 지 어떻게 알았어 요?외부 뷰 가 사건 을 차단 하면 내부 뷰 는 북서풍 도 마 시 지 못 하 는 것 이 아 닙 니까?서 두 르 지 마 세 요.구 글 공식 에 서 는 당연히 이런 상황 을 고려 하고 있 습 니 다.ViewGroup 에 requestDisallow InterceptTouchEvent 라 는 방법 이 있 습 니 다.이 방법 은 boolean 값 을 받 아들 이 는 것 입 니 다.ViewGroup 이 현재 이 벤트 를 차단 하 는 것 을 금지 하 는 지 여부 입 니 다.트 루 라면 이 뷰 그룹 은 사건 을 차단 할 수 없습니다.이 방법 이 있 으 면 우 리 는 내부 뷰 를 크게 놀 라 게 할 수 있다.내부 차단 법의 코드 를 보십시오:

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

  switch (event.getAction()) {
   case MotionEvent.ACTION_DOWN: {
   	//   parent  down  
    parent.requestDisallowInterceptTouchEvent(true);
    break;
   }
   case MotionEvent.ACTION_MOVE: {
    int deltaX = x - mLastX;
    int deltaY = y - mLastY;
    if (disallowParentInterceptTouchEvent) { //             Parent View    。
     parent.requestDisallowInterceptTouchEvent(false);
    }
    break;
   }
   case MotionEvent.ACTION_UP: {
    break;
   }
   default:
    break;
  }

  mLastX = x;
  mLastY = y;
  return super.dispatchTouchEvent(event);
 }
이렇게 처리 하면 두 개의 내장 View 가 조 화 롭 게 작업 할 수 있다.
다음은 외부 View 와 내부 View 에서 나 온 대화 입 니 다.
외부 보기:"사건 을 차단 하고 싶 습 니 다!"
내부 View:아니요,싫 습 니 다.이 사건 은 내 가 결정 할 것 이 니 예 수 는 그 를 붙 잡 을 수 없다.
3.ViewPager 2 의 미끄럼 충돌 처리
지난 장 에 서 는 미끄럼 충돌 처리 방안 두 가 지 를 설명 하 였 으 며,이 장 에 서 는 ViewPager 2 의 미끄럼 충돌 을 해결 하 겠 습 니 다.우선 차단 이 필요 하고 차단 이 필요 없 는 경계 조건 이 어디 에 있 는 지 확인 해 야 한다.이 글 을 쓰기 전에 Google 은 ViewPager 2 의 미끄럼 충돌 처리 방안 을 검 색 했 습 니 다.이 부분 에 대한 글 은 적지 않 지만 대부분의 글 은 ViewPager 2 의 미끄럼 충돌 처리 에 대해 완벽 하 게 고려 하지 못 했 습 니 다.
다음은 우리 가 상세 하 게 분석 해 보 자.
  • userInputEnable=false 가 설정 되 어 있다 면 ViewPager 2 는 어떠한 이벤트 도 차단 해 서 는 안 됩 니 다.
  • 아 이 템 이 하나 밖 에 없다 면 ViewPager 2 도 사건 을 차단 해 서 는 안 됩 니 다.
  • 여러 개의 Item 이 고 현재 첫 번 째 페이지 라면 왼쪽으로 미 끄 러 지 는 사건 만 차단 할 수 있 습 니 다.오른쪽으로 미 끄 러 지 는 사건 은 ViewPager 2 에 의 해 차단 되 어 서 는 안 됩 니 다.
  • 여러 개의 Item 이 고 현재 가 마지막 페이지 라면 오른쪽으로 미 끄 러 지 는 이벤트 만 차단 할 수 있 습 니 다.왼쪽으로 미 끄 러 지 는 이 벤트 는 현재 ViewPager 2 에서 차단 해 서 는 안 됩 니 다.
  • 여러 개의 Item 이 고 중간 페이지 라면 왼쪽 이 든 오른쪽 이 든 모두 ViewPager 2 가 차단 해 야 합 니 다.
  • 마지막 으로 ViewPager 2 는 수직 미끄럼 을 지원 하기 때문에 수직 미끄럼 도 상기 조건 을 고려 해 야 한다.
  • 경계 조건 을 분석 한 후에 우 리 는 어떤 방안 으로 미끄럼 충돌 을 처리 해 야 하 는 지 봅 시다.이곳 은 내부 차단 법 으로 처리 해 야 한 다 는 것 이 분명 하 다.그러나 ViewPager 2 가 final 로 설정 되 어 있 기 때문에 계승 을 통 해 처리 할 수 없 기 때문에 ViewPager 2 외부 에 사용자 정의 Layout 를 추가 해 야 합 니 다.이 Layout 는 사실 안쪽 View 와 바깥쪽 View 의 중간 에 끼어 있 는 것 과 같 습 니 다.사실은 이 Layout 가 안쪽 으로 변 했 습 니 다.자,쓸데없는 소리 하지 말고 코드 를 붙 여 라.
    
    class ViewPager2Container @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : RelativeLayout(context, attrs, defStyleAttr) {
    
     private var mViewPager2: ViewPager2? = null
     private var disallowParentInterceptDownEvent = true
     private var startX = 0
     private var startY = 0
    
     override fun onFinishInflate() {
      super.onFinishInflate()
      for (i in 0 until childCount) {
       val childView = getChildAt(i)
       if (childView is ViewPager2) {
        mViewPager2 = childView
        break
       }
      }
      if (mViewPager2 == null) {
       throw IllegalStateException("The root child of ViewPager2Container must contains a ViewPager2")
      }
     }
    
     override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
      val doNotNeedIntercept = (!mViewPager2!!.isUserInputEnabled
        || (mViewPager2?.adapter != null
        && mViewPager2?.adapter!!.itemCount <= 1))
      if (doNotNeedIntercept) {
       return super.onInterceptTouchEvent(ev)
      }
      when (ev.action) {
       MotionEvent.ACTION_DOWN -> {
        startX = ev.x.toInt()
        startY = ev.y.toInt()
        parent.requestDisallowInterceptTouchEvent(!disallowParentInterceptDownEvent)
       }
       MotionEvent.ACTION_MOVE -> {
        val endX = ev.x.toInt()
        val endY = ev.y.toInt()
        val disX = abs(endX - startX)
        val disY = abs(endY - startY)
        if (mViewPager2!!.orientation == ViewPager2.ORIENTATION_VERTICAL) {
         onVerticalActionMove(endY, disX, disY)
        } else if (mViewPager2!!.orientation == ViewPager2.ORIENTATION_HORIZONTAL) {
         onHorizontalActionMove(endX, disX, disY)
        }
       }
       MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> parent.requestDisallowInterceptTouchEvent(false)
      }
      return super.onInterceptTouchEvent(ev)
     }
    
     private fun onHorizontalActionMove(endX: Int, disX: Int, disY: Int) {
      if (mViewPager2?.adapter == null) {
       return
      }
      if (disX > disY) {
       val currentItem = mViewPager2?.currentItem
       val itemCount = mViewPager2?.adapter!!.itemCount
       if (currentItem == 0 && endX - startX > 0) {
        parent.requestDisallowInterceptTouchEvent(false)
       } else {
        parent.requestDisallowInterceptTouchEvent(currentItem != itemCount - 1
          || endX - startX >= 0)
       }
      } else if (disY > disX) {
       parent.requestDisallowInterceptTouchEvent(false)
      }
     }
    
     private fun onVerticalActionMove(endY: Int, disX: Int, disY: Int) {
      if (mViewPager2?.adapter == null) {
       return
      }
      val currentItem = mViewPager2?.currentItem
      val itemCount = mViewPager2?.adapter!!.itemCount
      if (disY > disX) {
       if (currentItem == 0 && endY - startY > 0) {
        parent.requestDisallowInterceptTouchEvent(false)
       } else {
        parent.requestDisallowInterceptTouchEvent(currentItem != itemCount - 1
          || endY - startY >= 0)
       }
      } else if (disX > disY) {
       parent.requestDisallowInterceptTouchEvent(false)
      }
     }
    
     /**
      *          View {@link MotionEvent#ACTION_DOWN}      View      ,   
      *     CoordinatorLayout+CollapsingToolbarLayout   ViewPager2Container          。
      *
      *        ViewPager2Container {@link MotionEvent#ACTION_DOWN}      View      ,   
      *     CoordinatorLayout+CollapsingToolbarLayout   ViewPager2Container          。
      *
      * @param disallowParentInterceptDownEvent     ViewPager2Container {@link MotionEvent#ACTION_DOWN}      View    ,    false
      *       true    ViewPager2Container {@link MotionEvent#ACTION_DOWN}      View     ,
      *         disallowIntercept true    CoordinatorLayout+CollapsingToolbarLayout     
      *       false   ViewPager2Container {@link MotionEvent#ACTION_DOWN}      View     ,
      */
     fun disallowParentInterceptDownEvent(disallowParentInterceptDownEvent: Boolean) {
      this.disallowParentInterceptDownEvent = disallowParentInterceptDownEvent
     }
    }
    위의 코드 는 지면 에 국한 되 어 있 습 니 다.저 는 너무 많은 설명 을 하지 않 겠 습 니 다.onFinish Inflate 에서 우 리 는 순환 을 통 해 ViewPager 2 Container 의 모든 하위 View 를 옮 겨 다 녔 습 니 다.ViewPager 2 를 찾 지 못 하면 이상 을 던 집 니 다.또 disallow Parent InterceptDownEvent 방법 에 대한 설명 은 자세히 쓰 여 있 지 않다.
    사용 방법 도 간단 합 니 다.ViewPager2Container 로 ViewPager 2 를 직접 감 싸 면 됩 니 다.
    
    <com.zhpan.sample.viewpager2.ViewPager2Container
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintLeft_toLeftOf="parent"
      app:layout_constraintRight_toRightOf="parent"
      app:layout_constraintTop_toTopOf="parent">
      
      <androidx.viewpager2.widget.ViewPager2
       android:id="@+id/view_pager2"
       android:layout_width="match_parent"
       android:layout_height="match_parent" />
    
      <com.zhpan.indicator.IndicatorView
       android:id="@+id/indicatorView"
       android:layout_centerHorizontal="true"
       android:layout_alignParentBottom="true"
       android:layout_margin="@dimen/dp_20"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>
    
     </com.zhpan.sample.viewpager2.ViewPager2Container>
    이것 은 ViewPager 2 미끄럼 충돌 에 대한 처리 방안 입 니 다.물론 Banner ViewPager 는 순환 순환 방송 을 지원 하기 때문에 Banner ViewPager 의 미끄럼 충돌 처리 가 상대 적 으로 번 거 로 울 수 있 습 니 다.관심 있 는 친구 가 있 으 면못 배 워 도 배 워 야 지!ViewPager 2 깊이 이해의 소스 코드 를 클릭 하여 볼 수 있다.
    동시에BannerViewPager의 소스 코드 도 Github 에 넣 었 고 필요 한 것 은 스스로 찾 을 수 있 습 니 다.
    이상 은 ViewPager 2 미끄럼 충돌 해결 방안 의 상세 한 내용 입 니 다.ViewPager 2 미끄럼 충돌 해결 에 관 한 자 료 는 저희 의 다른 관련 글 을 주목 해 주 십시오!

    좋은 웹페이지 즐겨찾기