Justaway for Android の並び替え機能の実装
ListViewをDrag and Dropで並び替えたかったので一から書いてみました。
サンプルソース: Sortable ListView on Drag and Drop
- サクサク並び替えたいのでドラッグ開始はタッチ(指が画面に触れた瞬間)を起点にしている
- ListView自体のスクロールを考慮する必要がある
という2つのポイントを抑えるため、リストの右端にソート用のハンドルをつけています。ハンドル以外のエリアはListViewのスクロール、ハンドルはソートという使い分ける為ですね。
スクロールを考慮する必要がなければハンドルではなく行全体を当たり判定にして良いでしょう。
サンプルソース解説付き
package com.example.sortable.app; import android.app.Activity; import android.content.Context; import android.graphics.Color; import android.os.Bundle; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.TextView; import java.util.ArrayList; public class MainActivity extends Activity { private MyArrayAdapter mAdapter; private ListView mListView; /** * ハンドルをタップするとtrue、 * 指が画面から離れるとfalseになります。 */ private boolean mSortable = false; /** * ドラッグ中のオブジェクトそのものです、 * これをremoveしたりinsertする事で並び替えを実現しています。 */ private String mDragString; /** * MotionEventは1pxでも動いたら発火します、 * リストを跨いだ時だけ処理するためにタップ位置を持っています。 */ private int mPosition = -1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mListView = (ListView) findViewById(R.id.listView); // いつものコード mAdapter = new MyArrayAdapter(this, R.layout.row_string); // ダミーデータもりもり for (int i = 0; i < 100; i++) { mAdapter.add("Dummy ".concat(String.valueOf(i))); } // いつものコード mListView.setAdapter(mAdapter); // 大事な所 mListView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent event) { if (!mSortable) { return false; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { break; } case MotionEvent.ACTION_MOVE: { /** * リストの何番目上をタップしているのか容易に取得できるのでとても簡素です。 */ int position = mListView.pointToPosition((int) event.getX(), (int) event.getY()); if (position < 0) { break; } /** * 指がリストを跨いだ瞬間に入れ替え */ if (position != mPosition) { mPosition = position; mAdapter.remove(mDragString); mAdapter.insert(mDragString, mPosition); } return true; } case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_OUTSIDE: { stopDrag(); return true; } } return false; } }); } /** * mDragString を設定した後に notifyDataSetChanged() する事で Adapter の getView が呼ばれ、 * mDragString とその行の String との比較によってハイライト状態が更新されます。 */ public void startDrag(String string) { mPosition = -1; mSortable = true; mDragString = string; mAdapter.notifyDataSetChanged(); } public void stopDrag() { mPosition = -1; mSortable = false; mDragString = null; mAdapter.notifyDataSetChanged(); } /** * ViewHolderパターン */ static class ViewHolder { TextView title; TextView handle; } public class MyArrayAdapter extends ArrayAdapter<String> { private ArrayList<String> mStrings = new ArrayList<String>(); private LayoutInflater mInflater; private int mLayout; public MyArrayAdapter(Context context, int textViewResourceId) { super(context, textViewResourceId); this.mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); this.mLayout = textViewResourceId; } @Override public void add(String row) { super.add(row); mStrings.add(row); } @Override public void insert(String row, int position) { super.insert(row, position); mStrings.add(position, row); } @Override public void remove(String row) { super.remove(row); mStrings.remove(row); } @Override public void clear() { super.clear(); mStrings.clear(); } @Override public View getView(final int position, View convertView, ViewGroup parent) { ViewHolder holder; View view = convertView; if (view == null) { view = mInflater.inflate(this.mLayout, null); assert view != null; holder = new ViewHolder(); holder.title = (TextView) view.findViewById(R.id.title); holder.handle = (TextView) view.findViewById(R.id.handle); view.setTag(holder); } else { holder = (ViewHolder) view.getTag(); } final String string = mStrings.get(position); holder.title.setText(string); /** * ドラッグハンドルのタップでソートを開始します * onClickでは指を離すまでイベントが発火しないのでドラッグ出来ません */ holder.handle.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) { startDrag(string); return true; } return false; } }); /** * ドラッグ行のハイライトです、力技ですね。 */ if (mDragString != null && mDragString.equals(string)) { view.setBackgroundColor(Color.parseColor("#9933b5e5")); } else { view.setBackgroundColor(Color.TRANSPARENT); } return view; } } }