AdapterView로 Custom List 만들기 – 3. Velocity, Runnable

딱 사용자가 드래그 한 만큼만 움직여서는 아무래도 리스트가 굼떠 보입니다.

마지막 드래그의 속도를 알아내서 드래그가 끝나더라도 자동으로 일정 거리만큼 스크롤이 더 되게하면

좀 더 세련된 스크롤이 됩니다.

 

구현 방법은, 드래그 속도 측정을 위해서 VelocityTracker 를 이용하고,

 

드래그가 끝나는 시점에 Runnable 을 사용해서 자동 스크롤이 되도록 하는 것입니다.

 

 

예제 프로젝트 다운로드 : 


AdapterView로 Custom List 만들기 [4] – 애니메이션 효과

AdapterView로 Custom List 만들기 [3] – Velocity, Runnable  <<<<< 현재 위치

AdapterView로 Custom List 만들기 [2] – Touch event, Scrolling 

AdapterView로 Custom List 만들기 [1] – AdapterView 기본 구조


 

1. 스크롤을 세련되게 – VelocityTracker
 

터치 동작을 ACTION_DOWNACTION_MOVEACTION_UP 으로 구분하면

각 ACTION 별로 아래와 같이 동작합니다.

 

1. ACTION_DOWN – startTouch()

 – 변수 초기화

 – VelocityTracker 인스턴스 생성

 – 업데이트 예정된 Runnable 이 있다면 삭제

 

2. ACTION_MOVE – scrollList()

 – VelocityTracker 에 드래그 좌표 정보를 입력

 – 리스트 업데이트

 

3. ACTION_UP – endTouch()

 – VelocityTracker에서 속도값 추출

 – 드래그 동작 이후 auto scrolling을 위한 Runnable instance 생성, 실행

 

 

기존 Touch event listener 와 관련 소스를 아래와 같이 수정 했습니다.

 

 

     /**

     * 터치 이벤트 리스너

     */

    @Override

    public boolean onTouchEvent(MotionEvent event) {

        if (getChildCount() == 0) {

            return false;

        }

        

        processTouch(event);            // 터치 동작을 분석 후 드래그 동작 수행

        

        return true;

    }

    

    /**

     * Child view에서 터치 이벤트를 사용하더라도 리스트에서 터치 이벤트를 계속 사용할 수 있도록 함.

     */

    @Override

    public boolean onInterceptTouchEvent(final MotionEvent event) {

         processTouch(event);            // 터치 동작을 분석 후 드래그 동작 수행

        return false;

    }

   

 

 

   

    private float mStartX = 0;                        // 터치 이벤트 시작된 기준 좌표 X

    private float mStartY = 0;                        // 터치 이벤트 시작된 기준 좌표 Y

    private boolean isProcessed = false;    // 특정 event 처리 후 다른 이벤트 처리가 필요 없을 때 설정

    private static final int UNIT_PIXELS_PER_MILLI = 1;     // Velocity trackter 의 시간 단위 – 1밀리초

    

    private VelocityTracker mVelocityTracker;

    private Runnable mScrollingRunnable;

    

    /**

     * 터치 이벤트를 처리하는 메인 루틴

     */

    private void processTouch(MotionEvent event)

    {

        if(event.getAction()==MotionEvent.ACTION_DOWN) {

            startTouch(event);

        }

        else if(event.getAction()==MotionEvent.ACTION_MOVE && !isProcessed) {

            mVelocityTracker.addMovement(event);

            scrollList(mStartY – event.getY());

            

            mStartX = event.getX();        // 요청을 처리했으므로 터치 시작점 재설정

            mStartY = event.getY();

        }

        else if(event.getAction()==MotionEvent.ACTION_UP && !isProcessed) {

            endTouch(event);

        }

    }

    

    /**

     * 터치 이벤트 시작 루틴

     */

    private void startTouch(MotionEvent event) {

        // 기존 동작중인 스크롤용 Runnable이 더 이상 동작하지 않도록 설정

        removeCallbacks(mScrollingRunnable);

        mVelocity = 0f;

 

        // 터치 시작점 저장

    mStartX = event.getX();

    mStartY = event.getY();

    isProcessed = false;

 

    // VelocityTracker 초기화

        mVelocityTracker = VelocityTracker.obtain();

        mVelocityTracker.addMovement(event);

    }

    

    /**

     * 스크롤 된 화면으로 update 요청

     */

    private void scrollList(float offset) {

        if( !isDrawing ) {

            mScreenTopOffset += offset;

            requestLayout();

        }

    }

 

    private float mVelocity = 0f;        // 드래그 속도 측정값

    private long mPrevTime = 0;            // 이전 스크롤 처리 시간

    private static final float FRICTION_FACTOR = 0.8f;    // 화면 업데이트 될 때 마다 속도를 늦춰주는 상수

    private static final float VELOCITY_MIN_THRESHOLD = 0.5f;    // 속도가 이 값 이하일 경우 스크롤 중단

    private static final float DISTANCE_CONVERT_FACTOR = 2.5f;    // 속도로부터 스크롤 거리를 계산할 때 곱하는 상수, 값이 클 수록 스크롤 양이 커짐

    private static final long REDRAW_DELAY_IN_MILLI = 15;        // 다음 화면 갱신을 위한 대기시간 (밀리초)

    

    /**

     * 터치 이벤트 종료 루틴

     * 속도를 측정하고 자동 스크롤이 되도록 Runnable 생성

     */

    private void endTouch(MotionEvent event) {

        mVelocityTracker.computeCurrentVelocity(UNIT_PIXELS_PER_MILLI);

        mVelocity = mVelocityTracker.getYVelocity();  // [1]

        

        mVelocityTracker.recycle();

        mVelocityTracker = null;

 

        mPrevTime = AnimationUtils.currentAnimationTimeMillis();

        

        // 자동으로 스크롤이 되도록 Runnable 생성

        if (mScrollingRunnable == null) {

            mScrollingRunnable = new Runnable() { // [2]

                public void run() {

                    long time = AnimationUtils.currentAnimationTimeMillis();  // [4]

                    float distance = mVelocity * (time – mPrevTime) * DISTANCE_CONVERT_FACTOR;

                    

                    scrollList(distance*-1);        // 리스트 화면 업데이트 요청, 드래그 방향과 스크린 이동방향이 반대이므로 부호를 역전 시킴

 

                    mVelocity = mVelocity * FRICTION_FACTOR;    // 속도를 줄임.

                    mPrevTime = time;                // 시간 업데이트

                    if (mVelocity > VELOCITY_MIN_THRESHOLD || mVelocity < VELOCITY_MIN_THRESHOLD * -1) {

                        postDelayed(this, REDRAW_DELAY_IN_MILLI); // [5]

                    }

                }

            };

        }

 

        if (mVelocity > VELOCITY_MIN_THRESHOLD || mVelocity < VELOCITY_MIN_THRESHOLD * -1) {

            post(mScrollingRunnable); // [3]

        }

 

    }

 

 

드래그 시작할 때 VelocityTracker 인스턴스를 얻어오고

드래그 하는 동안에 VelocityTracker 업데이트를 해줍니다.

드래그 종료 시점에 Velocity를 얻어오고 Runnable을 만들어서 실행시켜 줍니다.

 

 

2. 스크롤을 세련되게 – Runnable 을 이용한 auto scrolling

 

터치 이벤트 리스너에서 ACTION_UP 이벤트를 받으면 드래그 속도 값을 가져왔습니다. [1]

 

이제 자동 스크롤을 하기 위한 Runnable instance를 생성하고 [2]

속도가 일정 값 이상이면 Runnable을 실행합니다. [3]

 

Runnable – run() 에서는 속도를 이용해서 스크롤 거리를 계산하고 화면을 업데이트 시킵니다.

[4] 에서 일반적으로 사용하는 System.currentTimeMillis() 대신에 

AnimationUtils.currentAnimationTimeMillis() 을 사용하는 이유는 프로세스가 바쁜 와중에도

보다 정확한 시간 차이를 계산할 수 있다고 들어서입니다.

 

속도가 일정 값 이하로 떨어지지 않았으면 계속해서 Runnable을 실행시킵니다.  [5]

 

 

이제 리스트 구현을 위한 기본은 모두 구현되었습니다.

마지막 다음 편에서는 스크롤 하는 동안 리스트 아이템에 몇 가지 효과를 더해서

스크롤 되는 동안 아이템 애니메이션이 되는 듯한 느낌을 낼 예정입니다.

 

 

 

Post Author: TORTUGA

TORTUGA
궁금하신 점은 새로 개편한 홈페이지의 QnA 게시판을 이용해주세요!!!!!!! http://www.hardcopyworld.com/gnuboard5/bbs/board.php?bo_table=qna

댓글 남기기

이메일은 공개되지 않습니다.