ユーザがソート可能なListViewをすこしリッチにしてみた
ネタ元は
こちら
ユーザがソート可能なListView http://d.hatena.ne.jp/vvakame/20100718#1279453854
id:vvakame さんが素晴らしいコードを書いていたので、勝手に改変しましたすこしリッチにしてみました。
ごめんなさい
変数名とかだいぶいじっちゃいました。
すこしだけ変えるつもりが、たくさん変えちゃった(えへ)
コメント少なくてごめんなさい。途中でくじけました。
apk
Android1.6-2.2
HT-03A(Android1.6)で動作確認しました。
DragonDropList.apk
使い方
足りないところ
ソート中にスクロールできないのでどうにかしたいけど、いい方法思いつかない
ソート用のボタンを押すと掴むことができるとか、長押しすると掴むことができるとか
スマートな方法ないですかね・・・?
ソース
DragnDropActivity.java
package jp.tomorrowkey.android.dragondroplist; import java.util.ArrayList; import java.util.Arrays; import jp.tomorrowkey.android.dragondroplist.DragnDropListView.SortableAdapter; import android.app.Activity; import android.content.Context; import android.graphics.Color; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.View.OnClickListener; import android.widget.ArrayAdapter; import android.widget.CheckBox; import android.widget.ImageView; import android.widget.TextView; public class DragnDropActivity extends Activity implements OnClickListener { private static String[] items = { "01.北海道", "02.青森県", "03.岩手県", "04.宮城県", "05.秋田県", "06.山形県", "07.福島県", "08.茨城県", "09.栃木県", "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.愛媛県", "39.高知県", "40.福岡県", "41.佐賀県", "42.長崎県", "43.熊本県", "44.大分県", "45.宮崎県", "46.鹿児島県", "47.沖縄県" }; private static final int SELECTED_BG_COLOR = Color.argb(128, 255, 255, 255); private static final int HOVER_BG_COLOR = Color.argb(128, 255, 102, 0); private ArrayList<String> array; private StringArrayAdapter adapter = null; private DragnDropListView list; private ImageView imgRemoveTile; private CheckBox chkSort; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); array = new ArrayList<String>(Arrays.asList(items)); adapter = new StringArrayAdapter(this, array); list = (DragnDropListView) findViewById(R.id.list); list.setAdapter(adapter); chkSort = (CheckBox) findViewById(R.id.chkSort); chkSort.setOnClickListener(this); imgRemoveTile = (ImageView) findViewById(R.id.imgRemoveTile); list.setRemoveTile(imgRemoveTile); } @Override public void onClick(View v) { boolean sortable = chkSort.isChecked(); list.setSortMode(sortable); if (sortable) { imgRemoveTile.setVisibility(View.VISIBLE); } else { imgRemoveTile.setVisibility(View.GONE); } } class StringArrayAdapter extends ArrayAdapter<String> implements SortableAdapter { private ArrayList<String> list; private LayoutInflater inflater; private int selectedPosition = -1; private int hoverPosition = -1; public StringArrayAdapter(Context context, ArrayList<String> list) { super(context, R.layout.row, list); this.list = list; inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } public View getView(int position, View convertView, ViewGroup parent) { View view = null; ViewHolder holder = null; if (convertView == null) { view = inflater.inflate(R.layout.row, null); holder = new ViewHolder(); holder.txtString = (TextView) view.findViewById(R.id.txtString); view.setTag(holder); } else { view = convertView; holder = (ViewHolder) view.getTag(); } if (selectedPosition == hoverPosition) { if (position == selectedPosition) { view.setBackgroundColor(HOVER_BG_COLOR); } else { view.setBackgroundResource(android.R.drawable.list_selector_background); } } else { if (position == selectedPosition) { view.setBackgroundColor(SELECTED_BG_COLOR); } else if (position == hoverPosition) { view.setBackgroundColor(HOVER_BG_COLOR); } else { view.setBackgroundResource(android.R.drawable.list_selector_background); } } holder.txtString.setText(list.get(position)); return view; } @Override public void setSelectedPosition(int position) { if (selectedPosition != position) { selectedPosition = position; notifyDataSetChanged(); } } @Override public void setHoverPosition(int position) { if (hoverPosition != position) { hoverPosition = position; notifyDataSetChanged(); } } } class ViewHolder { TextView txtString; } }
DragnDropListView.java
package jp.tomorrowkey.android.dragondroplist; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.widget.Adapter; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.ListAdapter; import android.widget.ListView; public class DragnDropListView extends ListView { private static final String TAG = DragnDropListView.class.getSimpleName(); private static final boolean DEBUG = false; private static final int SCROLL_SPEED_FAST = 25; private static final int SCROLL_SPEED_SLOW = 8; private static final int MOVING_ITEM_BG_COLOR = Color.argb(128, 0, 0, 0); private static final int HOVER_REMOVE_ITEM_BG_COLOR = Color.argb(200, 255, 0, 0); private boolean sortMode = false; private DragListener mDrag = new DragListenerImpl(); private DropListener mDrop = new DropListenerImpl(); private RemoveListener mRemove = new RemoveListenerImpl(); public DragnDropListView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DragnDropListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } private SortableAdapter adapter; private Bitmap mDragBitmap = null; private ImageView mDragView = null; private WindowManager.LayoutParams mWindowParams = null; private int mFrom = -1; private View mRemoveTile = null; private Rect mRemoveHit = null; @Override public boolean onTouchEvent(MotionEvent event) { if (!sortMode) { return super.onTouchEvent(event); } int index = -1; final int x = (int) event.getX(); final int y = (int) event.getY(); int action = event.getAction(); if (action == MotionEvent.ACTION_DOWN) { index = pointToIndex(event); if (index < 0) { return false; } mFrom = index; startDrag(); adapter.setSelectedPosition(index); return true; } else if (action == MotionEvent.ACTION_MOVE) { final int height = getHeight(); final int fastBound = height / 9; final int slowBound = height / 4; final int center = height / 2; int speed = 0; if (event.getEventTime() - event.getDownTime() < 500) { // 500ミリ秒間はスクロールなし } else if (y < slowBound) { speed = y < fastBound ? -SCROLL_SPEED_FAST : -SCROLL_SPEED_SLOW; } else if (y > height - slowBound) { speed = y > height - fastBound ? SCROLL_SPEED_FAST : SCROLL_SPEED_SLOW; } if (DEBUG) { Log.d(TAG, "ACTION_MOVE y=" + y + ", height=" + height + ", fastBound=" + fastBound + ", slowBound=" + slowBound + ", center=" + center + ", speed=" + speed); } View v = null; if (speed != 0) { // 横方向はとりあえず考えない int centerPosition = pointToPosition(0, center); if (centerPosition == AdapterView.INVALID_POSITION) { centerPosition = pointToPosition(0, center + getDividerHeight() + 64); } v = getChildByIndex(centerPosition); if (v != null) { int pos = v.getTop(); setSelectionFromTop(centerPosition, pos - speed); } } if (mDragView != null) { if (mDragView.getHeight() < 0) { mDragView.setVisibility(View.INVISIBLE); } else { mDragView.setVisibility(View.VISIBLE); if (this.isHitRemoveTile(x, y)) { mDragView.setBackgroundColor(HOVER_REMOVE_ITEM_BG_COLOR); } else { mDragView.setBackgroundColor(MOVING_ITEM_BG_COLOR); } } mWindowParams.x = getLeft(); mWindowParams.y = getTop() + y; WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); wm.updateViewLayout(mDragView, mWindowParams); if (mDrag != null) { index = pointToIndex(event); mDrag.drag(mFrom, index); } adapter.setHoverPosition(index); return true; } } else if (action == MotionEvent.ACTION_UP) { if (isHitRemoveTile(x, y)) { // 削除イメージにヒットしていた場合、削除する if (mRemove != null) mRemove.remove(mFrom); } else { // 削除イメージにヒットしていなければ、要素の入れ替えをする if (mDrop != null) { index = pointToIndex(event); mDrop.drop(mFrom, index); } } // ドラッグ中の項目の表示を削除する endDrag(); adapter.setHoverPosition(-1); adapter.setSelectedPosition(-1); return true; } else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_OUTSIDE) { // ドラッグ中の項目の表示を削除する endDrag(); adapter.setHoverPosition(-1); adapter.setSelectedPosition(-1); return true; } else { Log.d(TAG, "Unknown event action=" + action); } return super.onTouchEvent(event); } /** * ドラッグを開始したときに呼び出される<br /> * ドラッグ中の項目を表示する */ private void startDrag() { WindowManager wm; View view = getChildByIndex(mFrom); final Bitmap.Config c = Bitmap.Config.ARGB_8888; mDragBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), c); Canvas canvas = new Canvas(); canvas.setBitmap(mDragBitmap); view.draw(canvas); if (mWindowParams == null) { mWindowParams = new WindowManager.LayoutParams(); mWindowParams.gravity = Gravity.TOP | Gravity.LEFT; mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT; mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT; mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; mWindowParams.format = PixelFormat.TRANSLUCENT; mWindowParams.windowAnimations = 0; mWindowParams.x = 0; mWindowParams.y = 0; } ImageView v = new ImageView(getContext()); v.setBackgroundColor(MOVING_ITEM_BG_COLOR); v.setImageBitmap(mDragBitmap); wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); if (mDragView != null) { wm.removeView(mDragView); } wm.addView(v, mWindowParams); mDragView = v; } /** * ドラッグアンドドロップが終了したときに呼び出される<br /> * ドラッグ中の項目の表示を削除する */ private void endDrag() { if (mDragView == null) { return; } WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); wm.removeView(mDragView); mDragView = null; // リサイクルするとたまに死ぬけどタイミング分からない // Desireでおきる。ht-03aだと発生しない // mDragBitmap.recycle(); mDragBitmap = null; } /** * 指定された座標が削除画像に被っているか返す * * @param x * @param y * @return */ private boolean isHitRemoveTile(int x, int y) { if (mRemoveTile != null && mRemoveTile.getVisibility() == View.VISIBLE) { if (mRemoveHit == null) { mRemoveHit = new Rect(); } mRemoveTile.getHitRect(mRemoveHit); if (mRemoveHit.contains(x + getLeft(), y + getTop())) { return true; } else { return false; } } else { return false; } } /** * 指定された要素番号のViewを返す<br /> * 要素番号はソースとなるListの要素番号を指定する * * @param index * @return */ private View getChildByIndex(int index) { return getChildAt(index - getFirstVisiblePosition()); } /** * MotionEventから要素番号に変換する * * @param ev * @return */ private int pointToIndex(MotionEvent event) { return pointToIndex((int) event.getX(), (int) event.getY()); } /** * 座標から要素番号に変換する * * @param x * @param y * @return */ private int pointToIndex(int x, int y) { return (int) pointToPosition(x, y); } /** * 削除画像の設定 * * @param v */ public void setRemoveTile(View v) { mRemoveTile = v; } public void setOnDragListener(DragListener listener) { mDrag = listener; } public void setOnDropListener(DragListener listener) { mDrag = listener; } public void setOnRemoveListener(RemoveListener listener) { mRemove = listener; } public interface DragListener { public void drag(int from, int to); } public interface DropListener { public void drop(int from, int to); } public interface RemoveListener { public void remove(int which); } class DragListenerImpl implements DragListener { public void drag(int from, int to) { if (DEBUG) { Log.d(TAG, "DragListenerImpl drag event. from=" + from + ", to=" + to); } } } public class DropListenerImpl implements DropListener { @SuppressWarnings("unchecked") public void drop(int from, int to) { if (DEBUG) { Log.d(TAG, "DropListenerImpl drop event. from=" + from + ", to=" + to); } if (from == to || from < 0 || to < 0) { return; } Adapter adapter = getAdapter(); if (adapter != null && adapter instanceof ArrayAdapter) { ArrayAdapter arrayAdapter = (ArrayAdapter) adapter; Object item = adapter.getItem(from); arrayAdapter.remove(item); arrayAdapter.insert(item, to); } } } public class RemoveListenerImpl implements RemoveListener { @SuppressWarnings("unchecked") public void remove(int which) { if (DEBUG) { Log.d(TAG, "RemoveListenerImpl remove event. which=" + which); } if (which < 0) { return; } Adapter adapter = getAdapter(); if (adapter != null && adapter instanceof ArrayAdapter) { ArrayAdapter arrayAdapter = (ArrayAdapter) adapter; Object item = adapter.getItem(which); arrayAdapter.remove(item); } } } /** * 並び替えをするかを設定する * * @param sortMode */ public void setSortMode(boolean sortMode) { this.sortMode = sortMode; } /** * 並び替えをするかを返す * * @return */ public boolean isSortMode() { return sortMode; } /** * ListViewにAdapterを設定する<br /> * Sortableを実装したAdapter以外を受け付けません * * @param adapter */ @Override public void setAdapter(ListAdapter adapter) { if (adapter instanceof SortableAdapter) { this.adapter = (SortableAdapter) adapter; super.setAdapter(adapter); } else { throw new RuntimeException("AdapterはSortableを実装する必要があります"); } } interface SortableAdapter { void setSelectedPosition(int position); void setHoverPosition(int position); } }
main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <CheckBox android:text="sort toggle" android:id="@+id/chkSort" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <ImageView android:id="@+id/imgRemoveTile" android:src="@drawable/remove_tile" android:background="#80FF0000" android:scaleType="center" android:visibility="gone" android:layout_margin="2dip" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <jp.tomorrowkey.android.dragondroplist.DragnDropListView android:id="@+id/list" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </LinearLayout>
row.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TextView android:id="@+id/txtString" android:textSize="24dip" android:layout_width="fill_parent" android:layout_height="wrap_content" /> </LinearLayout>
remove_tile.png
質問とかあったら
twitterで答えるよ!!