Tistory View

Android Develop/Input

안드로이드 Touch다루기 : Scroller

God Dangchy What should I do? 2020. 1. 2. 08:22

Scroller는 View같은 것이 아니고 그냥 단순히 계산기이다.

 

 

ScrollView나 Listview, RecyclerView 등에서 사용자가 손가락튕기기(플링?)을 한다던가 하면, 화면이 움직이는 데, 이 화면의 움직임이 시작시에는 빠르게 움직이다가 끝날 때 쯤은 천천히 멈추는 경우가 많다. 이런 것들을 Elastic Ease라고 하는데, 이 걸 산수로 현재의 보여질 위치를 계산해 내야한다. 수학을 잘하는 사람이야 쉽게 만들 수 있지만, 초보자 특히 수포자들에게 이 작업은 그리 쉽지는 않다. 안드로이드에서는 이 작업을 바로 할 수 있게 Class를 하나 제공해 주고 있다.

 

Scroller라는 Class인데, 워낙 스크롤할 때 많이 써서 이런 이름이 붙은 듯하다. 스크롤 이외에도 사용처는 많으니 이 Scroller사용법을 알아 보도록 하자. 뭐 여담이지만, 위에서 언금한 ScrollView나 RecyclerView같은 경우는 이미 이 Scroller를 이용해서 만들어 놨기 때문에, 이런 뷰들에서는 사용할 필요가 없지만, 커스텀뷰를 만들던가.. 아니면 이런 움직임이 필요한 곳에서는 이 것으로 만들어줘야 한다.

 

 

함수

startScroll(int startX, int startY, int dx, int dy, int duration)

계산을 시작하는 함수다.

start?는 시작값을 넣는 곳이고 d?는 시작으로부터 얼마나 이용을 시킬 것인지를 넣는 곳이다.

x와 y값을 따로 처리할 수 있지만 보통 두개를 같이 쓸일은 별로 없지만, 두개를 쓸 수는 있다.

만약 최종위치를 먼저 계산했다면, d? = 최종위치 - start? 가 된다.

 

 

computeScrollOffset()

startScroll함수에 지정한 값을 이 함수의 실행시간을 기준으로 값을 계산해 준다. 이 함수를 호출해야 실제 계산이 일어난다.

리턴값이 중요한데, 리턴값은 시간이 다 되서 스크롤이 끝이 났는지 아닌지를 알려주는 데, true일 경우 아직 스크롤이 진행중이고 false의 경우 스크롤이 끝난 것이다.

 

 

 

따라서, 보통 코드는 다음과 같이 작성된다.

 

코드

if( scroller.computeScrollOffset() ) {

     // 끝나지 않았으니, 다음에 또 호출이 되도록 한다.

     // 보통 다음의 함수를 쓰게 될 것이고, 필요하다면 Choreographer를 이용할 수도 있다.

     // ViewCompat.invalidateOnAnimation(this);

     // requrestRender(); // SurfaceView, TextureView등

}

int x = scroller.getCurrX(); // 이 x값이 현을 이용하여 작업을 하면된다.

 

 

스크롤이 끝이 났는 지 아닌지를 알아보는 또 다른 함수는 isFinished()함수를 이용하면 된다.

 

 

강제중지

[scroller].forceFinished( true );

만약 스크롤이 진행되는 중에 강제중지를 하려면 forceFinished함수를 호출하면 된다. 함수 이름과는 좀 맞지 않게 파라미터를 true로 설정해야 한다. false일 경우 중지하는 것이 아니고 중지를 끄는(?) 상황이 된다.

 

[scroller].abortAnimation();

이 것도 강제중지를 하는 함수다. 차이점은 forceFinished는 그냥 끝내버리고 getCurr?()함수를 호출하면 중간에 멈춘 상태값을 돌려주지만, 이 함수는 마지막 지점으로 이동을 시킨다는 것이다.

 

 

 

fling을 지원한다.

fling( int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY );

파라미터가 참 많다.. 하지만 x, y 두개로 나뉘어져 있을 뿐 3개밖에 되지 않는다.

 

start? : 시작위치

velocity? : 손꾸락 튕김속도(pixels/sec)

min? : 최소값이며 이 값보다 작은 값으로 계산이 되면 getCurr?함수가 이 값을 돌려주게 된다.

max? : 최대값이며 이 값보다 큰 값이 되면, getCurr?함수가 이 값을 돌려준다.

 

 

이 함수를 사용하려면 velocity를 구해야 하는 데, 이 걸 구하는 게.. 그리 쉬운 작업은 아니다.

 

다행히 이전에 배운 GestureDectector가 이 계산을 해주고 있다.

이전편의 GetstureDectector의 oFling함수(scroller의 fling이 아니다.)의 원형은 다음과 같다.

 

boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);

 

이 함수가 호출될 때, 이 velocity?값을 scroller의 fling에 넘겨 주면 된다. 보통다음과 같은 코드가 될 것이다.

아래의 예제는 X값은 사용하지 않고 Y값만 사용하도록 했다.

 

boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {

       int curY  = 어딘가에 저장해두거나 저장되어있는 현재위치;

       int minY = 0;  // 스크롤 할 수 있는 최소 값

       int maxY = 스크롤 할 수있는 최대값;

       scroller.fling( 0, curY, 0, (int)velocityY, 0, 0, minY, maxY );

}

 

나머지부분은 computeScroll을 이용하고 사용법도 동일하다.

 

필자가 파악한 바로는 이 fling에는 두가지 문제가 있다

 

첫번째는 [gesture].onFling함수는 velocity가 float으로 넘어왔지만, [scroller].fling에 넘길 때는 int로 변환되면서 정확한 값으로 넘어가지 않는다. 이 것은 큰 문제가 되지는 않는다. 대부분에 1px이하의 아주 작은 차이일 뿐이다. 솔직히 문제라고 보기에는... 그럼에도 필자와 같이 쓸데없이 완벽주의자(?) 성향이 있다면, curY값에 1024.0F를 곱하여 scroller.fling함수에 넘기고 getCurrY()함수의 결과를 다시 1024로 나누어[ getCurrY() >> 10] 오차를 확연히 줄일 수이다.. 당연히 전체 범위가 2백만을 넘지 않는 다는 가정에서만 써야 한다.

 

두번째 문제는 maxY값에 있다. 스크롤이 되는 화면에서 맨 아래부분 근처에서 손가락을 위로 튕기면(아래로 스크롤 하는 것) 빨리 탁!하고 마지막에 도착해서 멈출것이라 생각되지만 실제로는 그냥 스물~스물~ 천~천~히 스크롤이 되는 문제다. curY값과 maxY값이 별차이가 나지않아 생기는 문제다. 이 문제의 해결책은 maxY값을 최대 범위보다 더 큰 값을 주고 getCurrY()값을 검사하여 최대치로 강제로 조정해주면 된다.

 

이 두가지 문제를 해결한 코드는 다음과 같다. 첫번째 문제는 별로 문제거리가 아니므로, 필요없다면 사용하지 않아도 된다. 10000이라는 값은 적당히 변경하여 쓰면 되는 데, 결론은 10000이 젤 좋은 것 같다. 10000이라는 고정값을 적용했기 때문에 시작위치와 상관없이 빠른 fling처리가 된다.

 

boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {

       int curY  = 어딘가에 저장해두거나 저장되어있는 현재위치;

       int minY = curY - 10000;

       int maxY = curY + 10000;

       scroller.fling( 0, curY, 0, (int)( velocityY *  1024.0F), 0, 0, minY, maxY );

}

 

// getCurrY()

int y = getCurrY() >> 10;  // float을 쓴다면, [getCurrY() / 1024.0F]

if( y < scrollMin ) y = scrollMin; // scrollMin : 스크롤할 수 있는 최소 위치값

if( y > scrollMax ) y = scrollMax; // scrollMin : 스크롤할 수 있는 최대 위치값

 

 

 

Scroller vs ScrollerCompat vs OverScroller

사실상 3가지 모두 같은 것이다. 사용법이 아주 약간 차이가 있다.

Scroller : API 1

ScrollerCompat : API 22~API 26(Deprecated)

OverScroller : API 9 ~

 

어쩌라구? -> OverScroller를 사용한다. 끝~

현시점에서 구글이 이 걸 쓰라고 문서에 되어있다. 또 언제 ScrollerCompat2가 나올지도 모른다. 아휴 지겨워..

 

 

<< GestureDetector Velocity Tracker >>
Replies
Reply Write