山崎屋の技術メモ

IT業界で働く中でテクノロジーを愛するSIerのシステムエンジニア👨‍💻 | AndroidとWebアプリの二刀流🧙‍♂️ | コードの裏にあるストーリーを綴るブログ執筆者✍️ | 日々進化するデジタル世界で学び続ける探究者🚀 | #TechLover #CodeArtisan、気になること、メモしておきたいことを書いていきます。

【Android】RecyclerView つまみ(ハンドル)をドラッグして並び替え

RecyclerView の使い方、4シリーズ目です。

1シリーズ目:【Android】簡潔に RecyclerView を使う。 - 山崎屋の技術メモ
2シリーズ目:【Android】イメージを含んだリッチな行を持つ RecyclerView - 山崎屋の技術メモ
3シリーズ目:【Android】RecyclerView、行をドラッグして並び替え - 山崎屋の技術メモ

前回(3シリーズ目)は行自体を長押ししてドラッグという方法で並び替えを行いました。

今回はつまみ(ハンドル)をドラッグして並び替えを行います。行の長押しドラッグは無効にします。

つまみ(ハンドル)とは ↓↓↓ のようなイメージです。
f:id:yyama1556:20210217101555p:plain

完成形はこちらです。


f:id:yyama1556:20210217152327g:plain

それでは始めましょう。

Empty Activity のテンプレートを使用した NewProject を作成しておいてください。

イメージリソースの準備

プラスボタン(btn_add.png)、マイナスボタン(btn_minus.png)、つまみ(ハンドル)(ic_baseline_drag_handle_24.xml)のイメージは準備しておいてください。

f:id:yyama1556:20210217131446p:plain

ic_launcher_background.xml と ic_launcher_foreground.xml はプロジェクト作成時に自動で追加されたものです。

依存関係を追加

recyclerview のライブラリを依存関係に追加します。

モジュールの build.gradle の dependencies ブロックに次の一文を追加します。

    implementation 'androidx.recyclerview:recyclerview:1.1.0'


1 行のレイアウト xml を作成

リストの 1 行を表すレイアウト xml を追加します。

counter_row.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center_vertical"
    android:orientation="horizontal"
    android:padding="10dp">

    <!-- 並び替えつまみ(ハンドル) -->
    <androidx.appcompat.widget.AppCompatImageButton
        android:id="@+id/handle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="10dp"
        android:padding="0dp"
        android:src="@drawable/ic_baseline_drag_handle_24" />

    <!-- マイナスボタン -->
    <androidx.appcompat.widget.AppCompatImageButton
        android:id="@+id/btn_minus"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="0dp"
        android:adjustViewBounds="true"
        android:foreground="?android:attr/selectableItemBackground"
        android:padding="0dp"
        android:src="@drawable/btn_minus" />

    <!-- テキスト -->
    <TextView
        android:id="@+id/counter_num"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center_vertical|center_horizontal"
        android:text="1,000"
        android:textSize="40sp" />

    <!-- プラスボタン -->
    <androidx.appcompat.widget.AppCompatImageButton
        android:id="@+id/btn_plus"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:layout_margin="0dp"
        android:adjustViewBounds="true"
        android:foreground="?android:attr/selectableItemBackground"
        android:padding="0dp"
        android:src="@drawable/btn_add" />

</LinearLayout>


activity_main.xml を修正

RecyclerView を使うように修正します。

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

 

Adapter クラスを作成

 
RecycleView と データをつなぐためのアダプタを作成します。

MyAdapter.java

package org.yyama.recyclerviewsample4;

import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.recyclerview.widget.RecyclerView;

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
    private String[] myDataset;

    public static class MyViewHolder extends RecyclerView.ViewHolder {
        public TextView textView;
        public MyViewHolder(View v) {
            super(v);
            textView = v.findViewById(R.id.counter_num);
            textView.setTextSize(30);

            // つまみ(ハンドル)がタッチされたら、ドラッグできるようにする
            v.findViewById(R.id.handle).setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
                        MainActivity mc = (MainActivity) v.getContext();
                        mc.getItemTouchHelper().startDrag(MyViewHolder.this);
                    }
                    return false;
                }
            });
        }
    }

    public MyAdapter(String[] myDataset) {
        this.myDataset = myDataset;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.counter_row, parent, false);
        MyViewHolder vh = new MyViewHolder(v);
        return vh;
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        holder.textView.setText(myDataset[position]);
    }

    @Override
    public int getItemCount() {
        return myDataset.length;
    }
}


MainActivity.java を修正

最後に MainActivity を修正し RecyclerView にデータをセットします。

package org.yyama.recyclerviewsample4;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {
    private ItemTouchHelper itemTouchHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);
        String[] myDataset = {"aaa","bbb","ccc"};
        final RecyclerView.Adapter mAdapter = new MyAdapter(myDataset);
        recyclerView.setAdapter(mAdapter);
        RecyclerView.ItemDecoration itemDecoration =
                new DividerItemDecoration(this, DividerItemDecoration.VERTICAL);
        recyclerView.addItemDecoration(itemDecoration);

        // ドラックアンドドロップの操作を実装する
        itemTouchHelper = new ItemTouchHelper(
                // 上、下のドラッグを処理する Callback を作成する。スワイプは処理しない
                new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
                        ItemTouchHelper.ACTION_STATE_IDLE) {
                    @Override
                    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
                        final int fromPos = viewHolder.getAdapterPosition();
                        final int toPos = target.getAdapterPosition();
                        mAdapter.notifyItemMoved(fromPos, toPos);
                        return true;
                    }

                    @Override
                    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
                    }

                    // 行の長押しではドラッグできないようにする
                    @Override
                    public boolean isLongPressDragEnabled (){
                        return false;
                    }
                });
        
        // recyclerView と itemTouchHelper を紐づける
        itemTouchHelper.attachToRecyclerView(recyclerView);
    }
    public ItemTouchHelper getItemTouchHelper() {
        return itemTouchHelper;
    }
}

全体構成

モジュール、リソースの最終構成です。

f:id:yyama1556:20210217151256p:plain

まとめ

RecyclerView の並べ替えはデフォルトで行の長押し→ドラッグです。

今回は行の長押し→ドラッグは無効にし、つまみ(ハンドル)をドラッグして並べ替えする方法を紹介しました。

それでは!