안드로이드 RecyclerView에서 drag&drop과 swipe-to-dismiss를 구현한 예제는 많지만 View.OnDragListener를 이용한 경우가 많다. 이것은 예전 버전을 사용한 경우이고

새로운 API를 사용하거나 GestureDetector나 onIterceptTouchEvent를 이용하여 복잡아게 구현한 경우가 많다. 이번 포스트 에서는 Android Support Library를 이용하여

간단히 drag&drop과 swipe-to-dismiss를 구현한 방법을 알아보고자 한다.





ItemTouchHelper

ItemTouchHelper는 drag&drop과 swipe-to-dismiss를 구현하기에 매우 적합한 class이다. RecyclerView.ItemDecoration의 subclass이기 때문에 대부분의 LaoutManager와 Adapter에서 

쉽게 사용이 가능하다.


RecyclerView를 사용하려면 다음과 같이 build.gradle에 dependency를 추가해야 한다.


compile 'com.android.support:recyclerview-v7:22.2.0'





ItemTouchHelper와 ItemTouchHelper.Callback 사용


ItemTouchHelper를 사용하기 위해서는 ItemTouchHelper.Callback을 생성해야 한다.

이것은 "move"와 "swipe" 이벤트를 받을 수 있는 interface이다. 또한 기본 animation과 view의 선택된 상태를 제어할 수 있다.

기본적인 API로 이미 구현이 되어있지만 학습하는 과정이므로 새로운 SimpleCallback class를 만들어 보도록 한다.


callback class는 반드시 다음 3가지 메소드를 override해야 한다.


getMovementFlags(RecyclerView, ViewHolder)

onMove(RecyclerView, ViewHolder, ViewHolder)

onSwiped(ViewHolder, int)


다음 2가지도 사용한다.


isLongPressDragEnabled()

isItemViewSwipeEnabled()



이제 각각 메소드에 대해 하나씩 살펴보자.


1
2
3
4
5
6
7
@Override
public int getMovementFlags(RecyclerView recyclerView, 
        RecyclerView.ViewHolder viewHolder) {
    int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
    int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
    return makeMovementFlags(dragFlags, swipeFlags);
}
cs


ItemTouchHelper는 이벤트의 방향(direction)을 쉽게 찾을 수 있게 한다. 어떠한 방향으로 drag 혹은 swipe 되었는지 알려면 getMovementFlags() 를 반드시 override 해야 한다.

return 값으로 ItemTouchHelper.makeMovementFlags(int, int)를 사용하여 dragging 과 swiping를 양쪽 방향으로 사용할 수 있다.




1
2
3
4
5
6
public interface ItemTouchHelperAdapter {
 
    void onItemMove(int fromPosition, int toPosition);
 
    void onItemDismiss(int position);
}
cs


onMove() 와 onSwiped()는 데이터 변화에 따라 호출된다. 이러한 interface를 구성하여 각각 class를 연결시킨다.




1
2
3
4
@Override
public boolean isLongPressDragEnabled() {
    return true;
}
cs

ItemTouchHelper drag 없이 swipe 용도로만 사용가능하다(반대도 가능). RecyclerView item 에서 long press를 통해 drag 시점을 알기 위해서는 isLongPressDragEnabled()에서 true를 return 해야만 한다.

대신에 ItemTouchHelper.startDrag(RecyclerView.ViewHolder)를 사용할 수도 있지만 추후에 다루도록 한다.



1
2
3
4
@Override
public boolean isItemViewSwipeEnabled() {
    return true;
}
cs

View 안에서 touch 이벤트로 부터 swiping을 입력받게 하려면 isItemViewSwipeEnabled()에서 위와 마찬가지로 true를 반환해야 한다.

ItemTouchHelper.startSwipe(RecyclerView.ViewHolder)로 drag 이벤드를 시작할 수 있다.




위의 interface를 adapter에 적용시키면 다음과 같다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class RecyclerListAdapter extends 
        RecyclerView.Adapter<ItemViewHolder> 
        implements ItemTouchHelperAdapter {
...
 
@Override
public void onItemDismiss(int position) {
    mItems.remove(position);
    notifyItemRemoved(position);
}
 
@Override
public boolean onItemMove(int fromPosition, int toPosition) {
    if (fromPosition < toPosition) {
        for (int i = fromPosition; i < toPosition; i++) {
            Collections.swap(mItems, i, i + 1);
        }
    } else {
        for (int i = fromPosition; i > toPosition; i--) {
            Collections.swap(mItems, i, i - 1);
        }
    }
    notifyItemMoved(fromPosition, toPosition);
    return true;
}
cs


adapter 안에서 데이터의 변화는 notifyItemRemoved()와 notifyItemMoved()를 통하여 알 수 있다. 한가지 중요한 것은  데이터의 변화는 drag를 시작하고 drop 될 때 변화 하는것이 아니라 item이 움직일 때 마다 변화한다는 것이다.

그러므로 item이 움직일때 마다 adapter안에서 데이터의 변화가 일어나게 된다는 것을 명심하고 있어야 한다. 



다음으로 SimpleItemTouchHelperCallback에 onMove()와 onSwiped()를 orverride 해 줘야 한다. 생성자에 다음과 같이 adapter field를 추가한다.


1
2
3
4
5
6
private final ItemTouchHelperAdapter mAdapter;
 
public SimpleItemTouchHelperCallback(
        ItemTouchHelperAdapter adapter) {
    mAdapter = adapter;
}
cs



다음으로 onMove()와 onSwiped()를 추가한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public boolean onMove(RecyclerView recyclerView, 
        RecyclerView.ViewHolder viewHolder, 
        RecyclerView.ViewHolder target) {
    mAdapter.onItemMove(viewHolder.getAdapterPosition(), 
            target.getAdapterPosition());
    return true;
}
 
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, 
        int direction) {
    mAdapter.onItemDismiss(viewHolder.getAdapterPosition());
}
cs



Callback class의 최종 코드는 다음과 같다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
 
    private final ItemTouchHelperAdapter mAdapter;
 
    public SimpleItemTouchHelperCallback(ItemTouchHelperAdapter adapter) {
        mAdapter = adapter;
    }
    
    @Override
    public boolean isLongPressDragEnabled() {
        return true;
    }
 
    @Override
    public boolean isItemViewSwipeEnabled() {
        return true;
    }
 
    @Override
    public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
        int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
        return makeMovementFlags(dragFlags, swipeFlags);
    }
 
    @Override
    public boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, 
            ViewHolder target) {
        mAdapter.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
        return true;
    }
 
    @Override
    public void onSwiped(ViewHolder viewHolder, int direction) {
        mAdapter.onItemDismiss(viewHolder.getAdapterPosition());
    }
 
}
cs



이렇게 생성된 class들은 Activity나 Fragment에서 다음과 같이 사용한다. 


1
2
3
ItemTouchHelper.Callback callback =  new SimpleItemTouchHelperCallback(adapter);
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
touchHelper.attachToRecyclerView(recyclerView);
cs





+ Recent posts