banner
jzman

jzman

Coding、思考、自觉。
github

RecyclerViewによるドラッグ並べ替えとスワイプ削除機能の実装

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();
    }
}

表示効果#

GridLayoutManagerLinearLayoutManager
imageimage

RecyclerView のドラッグソートとスワイプ削除機能の実装はこれで終了です。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。