RecyclerView のドラッグソートには、ItemTouchHelper というクラスを利用する必要があります。ItemTouchHelper クラスは、Google が提供する RecyclerView のスワイプとドラッグをサポートするためのクラスです。以下に、このクラスを使用して RecyclerView のスワイプ削除とドラッグソート機能を実装します。
まず、ItemTouchHelper の内部抽象クラス Callback クラスについて簡単に説明します。
ItemTouchHelper.Callback#
このクラスは ItemTouchHelper クラス内の静的抽象クラスで、主に ItemTouchHelper をアプリケーションに関連付け、開発者が ViewHolder を通じて各 View の具体的な動作を制御し、ユーザーのイベントコールバックを受け取る役割を果たします。このクラスには、getMovementFlags、onMove、onSwiped の 3 つの抽象メソッドがあります。これらは開発中によく使用されます。
getMovementFlags#
このメソッドは、アイテムの 3 つの状態を示す Flags を返します:idle(アイドル)、swiping(スワイプ中)、dragging(ドラッグ中)。RecyclerView の異なるレイアウトマネージャに応じて、異なるスワイプおよびドラッグの方向を設定します。一般的には makeMovementFlags (int dragFlags, int swipeFlags) メソッドを使用して返され、dragFlags はドラッグの方向、swipeFlags はスワイプの方向を示します。
public abstract int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder);
onMove#
ItemTouchHelper がアイテムをドラッグすると、このメソッドがコールバックされ、アイテムは古い位置から新しい位置に移動します。ドラッグしない場合、このメソッドは決して呼び出されません。true を返すと、アイテムが新しい位置に移動したことを示します。
public abstract boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, ViewHolder target);
onSwiped#
アイテムがスワイプされたときに呼び出されます。スワイプしない場合、このメソッドは呼び出されません。direction を使用して、適切な判断を行い、特定の操作を実行できます。
public abstract void onSwiped(ViewHolder viewHolder, int direction);
また、よく使用されるメソッドには onSelectedChanged、clearView などがあります。
onSelectedChanged#
アイテムが静止状態からスワイプまたはドラッグ状態に変わると、このメソッドが呼び出されます。actionState を使用して、アイテムがどの状態にあるかを判断し、特定の操作を実行できます。このメソッドをオーバーライドする際は、必ず親クラスのメソッドを呼び出す必要があります。
public void onSelectedChanged(ViewHolder viewHolder, int actionState) {
if (viewHolder != null) {
sUICallback.onSelected(viewHolder.itemView);
}
}
clearView#
ユーザーとのインタラクションが終了したり、関連するアニメーションが完了した後にこのメソッドが呼び出されます。
public void clearView(RecyclerView recyclerView, ViewHolder viewHolder) {
sUICallback.clearView(viewHolder.itemView);
}
RecyclerView でのドラッグソートの実装#
RecyclerView のドラッグソートには、android.support.v7.widget.helper.ItemTouchHelper というクラスを利用して実装します。ドラッグソートの重要な部分は、インターフェース内の onMove (int fromPosition, int toPosition) メソッドです。GridAdapter 内の具体的な実装は以下の通りです。
@Override
public void onMove(int fromPosition, int toPosition) {
if (fromPosition < toPosition) {
for (int i = fromPosition; i < toPosition; i++) {
Collections.swap(list, i, i + 1);
}
} else {
for (int i = fromPosition; i > toPosition; i--) {
Collections.swap(list, i, i - 1);
}
}
notifyItemMoved(fromPosition, toPosition);
}
RecyclerView でのスワイプ削除の実装#
RecyclerView のドラッグソートには、android.support.v7.widget.helper.ItemTouchHelper というクラスを利用して実装します。スワイプ削除の重要な部分は、インターフェース内の onSwiped (int position) です。GridAdapter 内の具体的な実装は以下の通りです。
@Override
public void onSwiped(int position) {
Log.i("drag","onSwiped");
list.remove(position);
notifyItemRemoved(position);
}
参考コード#
ItemTouchHelper.Callback の実装クラス#
/**
* Created by jzman on 2017/5/17 0015.
*/
public class ItemTouchCallBack extends ItemTouchHelper.Callback {
private static final String TAG = "drag";
private OnItemTouchListener onItemTouchListener;
public void setOnItemTouchListener(OnItemTouchListener onItemTouchListener) {
this.onItemTouchListener = onItemTouchListener;
}
/**
* RecyclerView の異なるレイアウトマネージャに応じて、異なるスワイプおよびドラッグの方向を設定します。
* このメソッドは makeMovementFlags(int dragFlags, int swipeFlags) メソッドを使用して返されます。
* パラメータ: dragFlags: ドラッグの方向
* swipeFlags: スワイプの方向
* @param recyclerView
* @param viewHolder
* @return
*/
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
Log.i(TAG,"getMovementFlags");
if (recyclerView.getLayoutManager() instanceof GridLayoutManager ||
recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager){
//ここではスワイプ操作は必要ありません。4 と 8 以外の整数を設定できます。ここでは 0 に設定します。
//スワイプはサポートされていません。
return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN |
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT, 0 );
}else {
//LinearLayoutManager の場合、上方向と下方向のスワイプのみが可能です。
//ここでは、2 番目のパラメータを設定して右方向のスワイプをサポートします。
return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN , ItemTouchHelper.RIGHT );
}
}
/**
* ItemTouchHelper がアイテムをドラッグすると、このメソッドがコールバックされ、アイテムは古い位置から新しい位置に移動します。
* ドラッグしない場合、このメソッドは決して呼び出されません。true を返すと、アイテムが新しい位置に移動したことを示します。
* @param recyclerView
* @param viewHolder
* @param target
* @return
*/
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
Log.i(TAG,"onMove");
int fromPosition = viewHolder.getAdapterPosition();
int toPosition = target.getAdapterPosition();
onItemTouchListener.onMove(fromPosition,toPosition);
return true;
}
/**
* アイテムがスワイプされたときに呼び出されます。
* スワイプしない場合、このメソッドは呼び出されません。
* @param viewHolder
* @param direction
*/
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
Log.i(TAG,"onSwiped");
//ここがスワイプ削除の主要なコードです。
int position = viewHolder.getAdapterPosition();
onItemTouchListener.onSwiped(position);
}
/**
* アイテムがスワイプまたはドラッグされたときに呼び出されます。
* @param viewHolder
* @param actionState
*/
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
Log.i(TAG,"onSelectedChanged");
//...
super.onSelectedChanged(viewHolder, actionState);
}
/**
* ユーザーとのインタラクションが終了したり、関連するアニメーションが完了した後に呼び出されます。
* @param recyclerView
* @param viewHolder
*/
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
Log.i(TAG,"clearView");
//...
super.clearView(recyclerView, viewHolder);
}
/**
* 移動交換データの更新リスナー
*/
public interface OnItemTouchListener {
//アイテムをドラッグするときに呼び出されます。
void onMove(int fromPosition, int toPosition);
//アイテムをスワイプするときに呼び出されます。
void onSwiped(int position);
}
}
Adapter の実装#
/**
* Created by jzman on 2017/05/17 0009.
* RecyclerView の Adapter
*/
public class GridAdapter extends RecyclerView.Adapter<GridAdapter.DataViewHolder> implements View.OnClickListener,ItemTouchCallBack.OnItemTouchListener {
private Context context;
private List<SimpleTitleGrid> list;
public GridAdapter(Context context, List<SimpleTitleGrid> list) {
this.context = context;
this.list = list;
}
/**
* ViewHolder を作成します。
* @param parent
* @param viewType
* @return
*/
@Override
public DataViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//アイテムのレイアウトファイルを読み込みます(各アイテム)。
View view = LayoutInflater.from(context).inflate(R.layout.item,null);
//View にクリックイベントを設定します。
view.setOnClickListener(this);
DataViewHolder viewHolder = new DataViewHolder(view);
//コードを使用して幅と高さを設定します(xml レイアウト設定が無効な場合)。
view.setLayoutParams(new RecyclerView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
return viewHolder;
}
/**
* データをバインドします。
* @param holder
* @param position
*/
@Override
public void onBindViewHolder(DataViewHolder holder, int position) {
//各アイテムの高さを設定します。
holder.textView.setText(list.get(position).getTitle());
}
/**
* オプションの総数を返します。
* @return
*/
@Override
public int getItemCount() {
return list.size();
}
/**
* クリックイベント
* @param v
*/
@Override
public void onClick(View v) {
if(onItemClickListener!=null){
int position = recyclerView.getChildAdapterPosition(v);
//プログラムがここに到達すると、このメソッドの具体的な実装が実行されます。
onItemClickListener.onItemClick(recyclerView,v,position,list.get(position));
}
}
@Override
public void onMove(int fromPosition, int toPosition) {
if (fromPosition < toPosition) {
for (int i = fromPosition; i < toPosition; i++) {
Collections.swap(list, i, i + 1);
}
} else {
for (int i = fromPosition; i > toPosition; i--) {
Collections.swap(list, i, i - 1);
}
}
notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onSwiped(int position) {
Log.i("drag","onSwiped");
list.remove(position);
notifyItemRemoved(position);
}
/**
* RecyclerView に対して ViewHolder を実装します。
*/
public static class DataViewHolder extends RecyclerView.ViewHolder {
TextView textView;
public DataViewHolder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.tv_grid);
}
}
private RecyclerView recyclerView;
private OnItemClickListener onItemClickListener;
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
/**
* RecyclerView のオプションクリックイベントのコールバックインターフェースを設計します(外部で使用するため)。
* ListView のオプションクリックイベントメソッドを参考にしてください。
*/
public interface OnItemClickListener{
//パラメータ(親コンポーネント、クリックされた View、位置、ここでは特定のオブジェクトの id またはオブジェクトがあるかもしれません/ここでは必要ありません)。
void onItemClick(RecyclerView recyclerView, View view, int position, SimpleTitleGrid obj);
}
/**
* RecyclerView を Adapter にアタッチします。
*/
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
this.recyclerView= recyclerView;
}
/**
* RecyclerView を Adapter からデタッチします。
*/
@Override
public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
super.onDetachedFromRecyclerView(recyclerView);
this.recyclerView = null;
}
}
MainActivity#
/**
* Created by jzman on 2017/05/17 0029.
* RecyclerView の Adapter
*/
public class MainActivity extends AppCompatActivity implements GridAdapter.OnItemClickListener{
private RecyclerView rv_user;
private GridAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
rv_user = (RecyclerView) findViewById(R.id.rv_user);
adapter = new GridAdapter(this, DataUtils.getUserGrids());
ItemTouchCallBack touchCallBack = new ItemTouchCallBack();
touchCallBack.setOnItemTouchListener(adapter);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(touchCallBack);
rv_user.setLayoutManager(new GridLayoutManager(this,3));
// rv_user.setLayoutManager(new LinearLayoutManager(this));
rv_user.setAdapter(adapter);
itemTouchHelper.attachToRecyclerView(rv_user);
adapter.setOnItemClickListener(this);
}
@Override
public void onItemClick(RecyclerView recyclerView, View view, int position, SimpleTitleGrid obj) {
Toast.makeText(MainActivity.this, obj.getTitle(), Toast.LENGTH_SHORT).show();
}
}
表示効果#
GridLayoutManager | LinearLayoutManager |
---|---|
RecyclerView のドラッグソートとスワイプ削除機能の実装はこれで終了です。