티스토리 뷰

 ICS부터 추가된 목록을 왼쪽 또는 오른쪽으로 밀어내서 지우는 Swipe to Dismiss를 ListView에 적용하는 오픈 소스를 소개하려고 합니다. 구글에서 검색하면 "Stack Overflow"에 1개가 있습니다. 아래는 ICS이상에서 볼 수 있는 Swipe 기능입니다. 최근에는 Gamil 어플에도 포함되어 있구요. 아직 해당 기능에 대한 API가 제공되고 있지 않기에 만들어서 사용해야 합니다. 오픈소스가 있으니 쉽게 적용할 수도 있습니다.^^ 해당 방법을 간단하게 소개하려고 합니다.


Swipe to Dismiss 소스코드 다운로드

 해당 코드는 "Roman Nurik" 이라는 구글 UI 개발자 분이 만드셨습니다. 구글+ 페이지를 통해서 확인이 가능하며, GitHub에 소스코드가 등록되어 있습니다.

 구글 플러스 페이지 : https://plus.google.com/u/0/113735310430199015092/posts/Fgo1p5uWZLu

 GitHub : https://gist.github.com/2980593

 GitHub에 가셔서 다운로드 받으셔서 사용하시면 됩니다.


Swipe to Dismiss 적용하기

 적용방법은 중요 코드만 작성하겠습니다. 소스코드만 다운받으시면 자유롭게 사용이 가능합니다. 가능하시다면 수정 변경하셔도 좋을 듯합니다. 저는 처음에 Stack Overflow에서 올라온 소스코드로 적용을 했었습니다. 버그가 있어서 완벽히 동작하지는 않는 코드였습니다. 그러다가 GitHub에 올라온 소스코드를 다시 받아서 확인했더니 에러없이 동작합니다.

 필요한 파일 : SwipeDismissListViewTouchListener.java, SwipeDismissTouchListener.java 2가지의 파일입니다.

 중요 메소드

//SwipeDismissListView를 생성하기 위한 Method
SwipeDismissListViewTouchListener touchListener = new SwipeDismissListViewTouchListener(mListView, MainActivity.this);
//OnTouchListener, OnScrollListener를 등록
mListView.setOnTouchListener(touchListener);
mListView.setOnScrollListener(touchListener.makeScrollListener());

//interface로 상속받아야 할 Method
@Override
public void onDismiss(ListView listView, int[] reverseSortedPositions) {  }

 위와 같은 코드가 전부입니다. Swipe 동작방식을 간단히 말하면 1번째로 OnTouchListener를 통해 현재 위치의 자표값을 받습니다. 터치를 했을 때와 드래그했을 때 손을 땠을때의 3가지 Event를 받아서 처리하게 됩니다. 처음에 터치했을 때에 필요한 변수들을 모두 초기화합니다. Move 이벤트에서 해당 값을 읽어들입니다.(왼쪽 또는 오른쪽으로 X좌표가 움직이는지를 체크) 마지막으로 손을 땠을 때 마지막 위치의 값에 따라 Dismiss가 되도록 처리합니다.

 그런데 ListView의 경우에는 위 아래로 이동을 하게됩니다. 이를 방지하기 위해서 OnScrollListener의 값을 상태에 따라 저장하게 됩니다. 상하로 이동했는지 아닌지를 구분하여 최종적으로 Swipe to Dismiss인지 아닌지를 구분하게 됩니다.


주요 소스코드

 제가 작성한 예제의 메인코드입니다. Swipe 관련 코드들은 모두 GitHub에서 확인이 가능하니 제외하겠습니다.

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);
	mListView = (ListView)findViewById(R.id.listview);
		
         //ListView에 뿌려줄 값
	String[] items = new String[20];
	for(int i=0; i<20; i++) {
		items[i] = "List " + (i + 1);
	}
		
        //새로운 ArrayAdapter를 생성한다.
	mAdapter = new ArrayAdapter<string>(this, android.R.layout.simple_list_item_1, android.R.id.text1, new ArrayList<string>(Arrays.asList(items)));
	mListView.setAdapter(mAdapter);
		
        //SwipeDismissListView 객체를 생성한다. 이때 ListView와 Context를 함께 넘긴다.
	SwipeDismissListViewTouchListener touchListener = new SwipeDismissListViewTouchListener(mListView, MainActivity.this);
		
        //OnTouchListener와 OnScrollListener를 등록한다.
	mListView.setOnTouchListener(touchListener);
	mListView.setOnScrollListener(touchListener.makeScrollListener());
}

//implements SwipeDismissListViewTouchListener.OnDismissCallback 인터페이스를 상속받아 onDismiss의 행동을 정의한다.
@Override
public void onDismiss(ListView listView, int[] reverseSortedPositions) {
	for (int i=0; i<reverseSortedPositions.length; i++) {
		Log.v("MainActivity", i + " position " + reverseSortedPositions[i]);
		mAdapter.remove(mAdapter.getItem(reverseSortedPositions[i]));
	}
	mAdapter.notifyDataSetChanged();
}

 Callback 인터페이스를 상속받아서 정의하는 부분을 잘 보시면 넘어오는 값의 int가 그냥 int reverseSortedPositions가 아닌 int[]의 배열형태로 넘어오게 됩니다. 저는 처음에 왜 이렇게 처리해야 할까라는 생각을 해봤습니다. 해답은 간단햇습니다. 소스코드를 변경하고 앱을 테스트하면서 확인해보니 사용자가 순간적으로 여러개를 막 밀어내는 경우가 발생할 수 있습니다. 이럴 경우 1개의 행동이 끝나지 않은 상태로 마지막 이벤트를 받아서 처리해야 하게 되더군요. 이전에 넘겼던 값들은 처리하지 않고, 마지막 값만 처리하게되는 문제가 발생합니다. ListView에는 보이지 않지만 나중에 데이터가 다시 살아나는 경우가 발생했습니다. 이런 경우를 대비하기 위해서 위와 같이 int[] 배열형태로 값을 넘겨주게 됩니다.


결과 화면

 결과화면은 아래와 같습니다. "Roman Nurik"분이 직접 올려주신 코드 그대로 사용하셔도 되지만 테스트하는 것이니^^ 다양하게 적요이 가능합니다. 참고로 지난 예제로 올렸던 "ExpandableListView"에도 적용이 가능합니다. 수정은 해야합니다.^^;

 이상으로 SwipeExample 예제를 살펴봤습니다.


소스코드 다운로드

  http://db.tt/AAGh7bSg



댓글