Search

'아이폰처럼 한장'에 해당되는 글 1건

  1. 2011.06.06 안드로이드에서 아이폰 사진첩처럼 한장씩 넘어가는 갤러리 (18)
아래는 첨부된 소스 실행 동영상입니다. 보면 한장씩 자연스럽게 넘어갑니다. 앱 위에 보면 아이폰의 페이지 컨트롤처럼 보여지는 부분도 있는데, 그거까지 설명하면 글이 길어지니 일단 생략하겠습니다.
 


안드로이드 기본 뷰는 한장씩 넘어가는 게 안되서 ViewFlipper나 ViewSwitcher로 구현을 하게 되는데 손으로 드래그해서 스크롤되는 효과를 줄 수가 없게 되죠. 여기서는 Gallery뷰를 상속받아 스크롤이 미끄러질때 미끄러지는 속도를 딱 한장만 넘어갈 정도의 속도로 낮춰서 만들었습니다.

학창시절 물리시간에 배운 기억을 더듬어봐서 속도를 공식으로 표현하면 속도의 제곱은 2 * 마찰계수 * 중력가속도 * 이동거리가 됩니다. 물리에 관한 글이 아니므로 어떻게 유도했는지 자세한 설명은 생략하겠습니다.


일단 상수인 마찰계수와 중력가속도부터 구해야하는데 정보가 없으니 안드로이드 프레임워크 소스를 열어서 분석해봐야합니다. 실제로 저런식으로 구현됐는지도 확인해봐야하고요. 설마했는데 실제로 소스를 열어보니 실제로 지구의 중력가속도에 실제 픽셀을 거리로 변환해서 구현했습니다. (이렇게 구현한 것도 사실 이해가 안가는데 토성, 목성 같은 곳의 중력가속도 정보도 있습니다;;;)

public OneFlingGallery(Context context, AttributeSet attrs) {

super(context, attrs);

float ppi = context.getResources().getDisplayMetrics().density * 160.0f;

mDeceleration = SensorManager.GRAVITY_EARTH // g (m/s^2)

* 39.37f // inch/meter

* ppi // pixels per inch

* ViewConfiguration.getScrollFriction();

    }


중력가속도는 SensorManager.GRAVITY_EARTH * 39.37f * ppi
마찰계수는 ViewConfiguration.getScrollFriction() 입니다.
소스를 열어보면 마찰계수가 0.015로 되어있습니다. 저 마찰계수는 빙판과 아이스하키 공(퍽)의 마찰계수 수준입니다. 스크롤을 한번하면 멈추지 않고 쭉쭉 미끄러지는 게 이해가 가는군요. (기껏 물리학적으로 자연스럽게 구현해서 저런식으로 부자연스럽게 낮은 마찰계수를 적용했는지는 이해가 안갑니다만)

참고로 진저나 프로요 이하에서는 마찰계수가 프레임워크에 상수로 고정되어 있으나 API 11(3.0 허니콤) 부터는 setFriction메소드로 마찰계수의 변경이 가능합니다. 그때부터는 속도가 아니라 마찰계수를 바꾸면 되니 더 자연스럽게 구현을 할 수 있을 겁니다.

이제 이동거리를 구해봅시다. 갤러리같은 어댑터뷰는 사실 스크롤이 되는 게 아니라 자식뷰들을 반대쪽으로 이동시키는 구조로 되어 있습니다. getScrollX()로 구하면 항상 0이 나오기 때문에 onScroll을 오버로드해서 자식뷰들을 이동시킨 거리를 합해서 구해야합니다.

    @Override

    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,

    float distanceY) {

mDistanceX += distanceX;

return super.onScroll(e1, e2, distanceX, distanceY);

    }


    @Override

    public boolean onTouchEvent(MotionEvent event) {

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

    mDistanceX = 0;

}

return super.onTouchEvent(event);

    }

 
이렇게 하면 mDistanceX에 스크롤된 거리가 합산이 됩니다. onScroll을 오버로드 하지말고, onTouchEvent에서 이벤트거리만 합산해도 되지 않겠냐고 생각할지 모르지만 안됩니다. 마지막에 onScroll이벤트보다 fling이 먼저 발생하는데 그 때 1,2 픽셀정도 오차가 나게되고 한장씩 넘어가는 건 맞지만 딱 맞아떨어져서 멈추지 않기 때문에 묘하게 거슬립니다.

갤러리 크기에서 스크롤된 거리를 빼면 이동해야할 거리가 나옵니다.

    @Override

    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,

    float velocityY) {


float toMoveDistance = getWidth() - Math.abs(mDistanceX);

float maxVelocity = (float) Math.sqrt(toMoveDistance * mDeceleration

* 2);

float revisedVelocityX = 0;


if (velocityX > 0) {

    revisedVelocityX = Math.min(velocityX, maxVelocity);

} else {

    revisedVelocityX = Math.max(velocityX, -maxVelocity);

}


return super.onFling(e1, e2, revisedVelocityX, velocityY);

    } 


이렇게 구한 이동해야할 거리로 속도를 구합니다. 속도를 자연스럽게 구현할 때 속도를 낮추는 건 괜찮지만 빠르게 하는 건 이상하겠죠? 자연스럽게 하기위해 속도는 원래 속도와 이동해야할 속도 중 낮은 속도로 수정해줍니다. 이렇게 해서 가다가 멈춰도 알아서 적당한 위치로 붙기 때문에 속도를 낮춰도 괜찮습니다. 그리고 그렇게 구한 속도를 fling에 넣어주면 됩니다.

아래의 전체소스이용해서 갤러리를 만드시면 되고 사용법은 기존의 Gallery 뷰와 동일합니다.

전체소스 보기


첨부된 파일은 동영상처럼 아이폰의 페이지 컨트롤도 포함시켰고, 바로 전에 썼던 메모리관리 기법도 적용한 예제소스입니다. (이 부분도 설명할 부분이 있는데, 너무 방대해져서 그냥 소스만 첨부합니다. 나중에 기회되면 어댑터 내에서 메모리 관리하는 것도 설명할게요)

화면에 꽉차는 이미지 등장할 때부터 부터는 이미지가 좀 늦게 등장하는 현상이 있는데 그림파일 I/O 때문에 발생합니다. 다음에 I/O시간 때문에 UI가 멈추는 시간을 제거하는 기법을 설명하거나 갤러리 안에서 자연스럽게 세로스크롤 되는 기법을 설명하겠습니다.(첨부된 소스 안에 OneFlingScrollGallery는 세로스크롤 하는 갤러리 소스입니다)


소스가 첨부된 GitHub 링크
 
신고


 

티스토리 툴바