인디노트

Android ViewPager.OnPageChangeListener 의 이벤트 전달 순서 본문

소스 팁/Java, Android, Kotlin

Android ViewPager.OnPageChangeListener 의 이벤트 전달 순서

인디개발자 2016. 10. 29. 16:53

Android ViewPager.OnPageChangeListener 의 이벤트 전달 순서

이번에 작업하는 UI는 ViewPager의 0번 탭에서 1번 탭으로 이동할 때 특정한 애니메이션을 실행한다. 일반적인 탭이라면 별 복잡한 처리가 필요하지 않겠지만, ViewPager의 특성 상 0번 탭에서 1번 탭 이동이 swipe일 수도 있고, 탭을 꼭 집어 이동하는 케이스도 있다.

실제 케이스는 더 복잡하지만 일단 서두는 이 정도로 해 두고, ViewPager의 상태가 어떻게 바뀌어가는지 알아야 하기에 ViewPager.OnPageChangeListner 를 이용했다.

이 클래스는 onPageScrollStateChanged(int state)onPageScrolled(int position, float positionOffset, int positionOffsetPixels)onPageSelected(int position) 3개의 method를 가진다. 그런데 생각보다 이벤트 전달 순서가 복잡해서 이 순서를 잘 알고 있어야 엉뚱한 오류를 내지 않을 듯 하다.

1. Swipe (A->B)

  1. onScrollStateChanged: dragging
  2. onPageScrolled: 드래그 하는 동안 계속 호출
  3. 어느 정도 드래그 되었다면
  4. onScrollStateChanged: settling
  5. onPageSelected: B
  6. onPageScrolled: 제대로 안착할 때 까지 계속 호출
  7. onScrollStateChanged: idle

2. Swipe 중도 취소 (A->B로 가려다->A)

  1. onScrollStateChanged: dragging
  2. onPageScrolled: 드래그 하는 동안 계속 호출
  3. 중간에 취소되었다면 (손을 놓아버리는)
  4. onScrollStateChanged: settling
  5. onPageScrolled: 제대로 안착할 때 까지 계속 호출
  6. onScrollStateChanged: idle

1번 경우와 거의 비슷하지만 onPageSelected 가 settling 다음에 호출되지 않은 부분이 다르다.

3. 탭 선택 (A->B)

  1. onScrollStateChanged: settling
  2. onPageSelected: B
  3. onPageScrolled: 제대로 안착할 때 까지 계속 호출
  4. onScrollStateChanged: idle

onPageSelected 가 먼저 호출될 줄 알았는데 onScrollStateChanged 가 먼저 호출되었다.

내 문제의 요구사항은 다음과 같다.

  • 0번 탭에서 다른 탭을 선택할 경우 애니메이션 실행
  • 0번 탭에서 1번 탭으로 스와이프 할 경우 애니메이션 실행
    • 0번 탭에서 1번 탭으로 다가가 중도에 취소할 경우 반대 애니메이션 실행
  • 그 외의 경우엔 애니메이션 실행 안함

두번째 경우의 취소만 없었어도 복잡도가 훨씬 줄어들었을 것 같은데, 이것까지 고려하려다 보니 꽤나 복잡해졌다.

내 문제를 해결하기 위해선 각 경우를 판단할 수 있어야 해서 대략 다음과 같이 처리했다.

  1. onPageScrollStateChanged : state를 별도의 변수에 저장해 둔다. SCROLL_STATE_SETTLING 의 경우 이 다음에 바로 onPageSelected 가 호출되기 때문에 원래 어느 탭에서 시작된 애니메이션인지 식별하기가 어렵다. 따라서 이동을 시작하는 위치와 끝의 탭 위치를 알기 쉽게 하려고 prevPosition과 targetPosition 두개의 변수를 두었고, prevPosition 은 SCROLL_STATE_SETTLING 상태일 때 설정했다.
  2. onPageSelected : settle 대상 페이지가 지정된 경우 호출된다. 따라서 targetPosition 변수에 넘어온 값을 저장한다.
  3. onPageScrolled : 여기 넘어온 position 값 자체는 활용하기가 쉽지 않다. settle 상태에선 대상 페이지가 넘어오고, settle하기 전인 dragging 상태에선 출발 페이지가 넘어온다. 그래서 인자로 넘어온 페이지값은 그냥 무시한다.

대충 코드는 다음과 같다.


int scrollState=0;
int targetPage= 0;
int prevPage= 0;

public void onPageScrollStateChanged(int state) {
  if (pagerScrollState == ViewPager.SCROLL_STATE_SETTLING) {
    prevPage = targetPage;
  }
  scrollState = state;
}

public void onPageSelected(int position) {
  targetPage = position;
}

진짜배기는 여기부터인데, 0번에서 1번으로 한창 스와이프 중인지, 0번에서 1번으로 스와이프 하다가 취소해서 되돌아오는 중인지, 0번에서 1번으로 충분히 스와이프를 했거나 0번에서 1번으로 탭을 직접 눌러서 이동하는 중인지를 onPageScrolled 에서 판단을 해서 애니메이션 처리를 해야 했다.

알고 있는 정보로 케이스를 조합해보자면 다음과 같다.

  1. dragging 상태인데 target이 0이면 0번 탭에서 한창 손으로 끌고 있는 중이므로 해당.
  2. settling 상태인데 target, prev 모두 0이면 드래깅하다 관둔 경우이므로 해당.
  3. settling 상태인데 target=1, prev=0 이면 드래깅 충분히 해서 안착하는 경우이므로 해당.

boolean needAnimation() {
  switch(scrollState) {
     case SCROLL_STATE_DRAGGING: 
       if (targetPage == 0) {
         return true;
       }
       break;
     case SCROLL_STATE_SETTLING:
       if (targetPage == 1 && prevPage == 0 ) {
         return true;
       } else if( targetPage ==0 && prevPage ==0) {
         return true;
       }
     break;
  }
  return false;
}

내 문제에선 이것 말고도 몇가지 자질구래한 경우가 더 있어서 조건문이 몇개 더 붙긴 했지만 대략 이 정도 내용을 염두에 둔다면 pager의 tab 이동과 관련한 상태 검색은 충분히 구현할 수 있을 것이다.


반응형
Comments