package net.virifi.android.navigationbarsettings;
import android.app.Activity;
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.Window;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ImageView;
import android.widget.ListView;
public class SortableListView extends ListView implements
OnItemLongClickListener {
private static final int SCROLL_SPEED_FAST = 25;
private static final int SCROLL_SPEED_SLOW = 8;
private static final Bitmap.Config DRAG_BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
private boolean mSortable = false;
private boolean mDragging = false;
private DragListener mDragListener = new SimpleDragListener();
private int mBitmapBackgroundColor = Color.argb(128, 0xFF, 0xFF, 0xFF);
private Bitmap mDragBitmap = null;
private ImageView mDragImageView = null;
private WindowManager.LayoutParams mLayoutParams = null;
// private MotionEvent mActionDownEvent;
private int mPositionFrom = -1;
private float mActionDownEventX;
private float mActionDownEventY;
private int mContentViewTop;
private void getContentViewTop(Context context) {
Rect rect= new Rect();
Window window= ((Activity)context).getWindow();
window.getDecorView().getWindowVisibleDisplayFrame(rect);
//int statusBarHeight= rect.top;
mContentViewTop= window.findViewById(Window.ID_ANDROID_CONTENT).getTop();
//int titleBarHeight= contentViewTop - statusBarHeight;
}
/** コンストラクタ */
public SortableListView(Context context) {
super(context);
setOnItemLongClickListener(this);
getContentViewTop(context);
}
/** コンストラクタ */
public SortableListView(Context context, AttributeSet attrs) {
super(context, attrs);
setOnItemLongClickListener(this);
getContentViewTop(context);
}
/** コンストラクタ */
public SortableListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setOnItemLongClickListener(this);
getContentViewTop(context);
}
/** ドラッグイベントリスナの設定 */
public void setDragListener(DragListener listener) {
mDragListener = listener;
}
/** ソートモードの切替 */
public void setSortable(boolean sortable) {
this.mSortable = sortable;
}
/** ソート中アイテムの背景色を設定 */
@Override
public void setBackgroundColor(int color) {
mBitmapBackgroundColor = color;
}
/** ソートモードの設定 */
public boolean getSortable() {
return mSortable;
}
private int actionDownCoordToPosition() {
getContentViewTop(getContext());
int tmp = pointToPosition((int)this.mActionDownEventX, (int) this.mActionDownEventY);
return tmp - this.getFirstVisiblePosition();
}
/** MotionEvent から position を取得する */
private int eventToPosition(MotionEvent event) {
getContentViewTop(getContext());
int tmp = pointToPosition((int) event.getX(), (int) event.getY() - mContentViewTop);
return tmp - this.getFirstVisiblePosition();
}
/** タッチイベント処理 */
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!mSortable) {
return super.onTouchEvent(event);
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
//storeMotionEvent(event);
this.mActionDownEventX = event.getX();
this.mActionDownEventY = event.getY();
break;
}
case MotionEvent.ACTION_MOVE: {
if (duringDrag(event)) {
return true;
}
break;
}
case MotionEvent.ACTION_UP: {
if (stopDrag(event, true)) {
return true;
}
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_OUTSIDE: {
if (stopDrag(event, false)) {
return true;
}
break;
}
}
return super.onTouchEvent(event);
}
/** リスト要素長押しイベント処理 */
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view,
int position, long id) {
return startDrag();
}
/** ACTION_DOWN 時の MotionEvent をプロパティに格納 */
private void storeMotionEvent(MotionEvent event) {
//mActionDownEvent = event;
}
/** ドラッグ開始 */
private boolean startDrag() {
// イベントから position を取得
//mPositionFrom = eventToPosition(mActionDownEvent);
mPositionFrom = this.actionDownCoordToPosition();
// 取得した position が 0未満=範囲外の場合はドラッグを開始しない
if (mPositionFrom < 0) {
return false;
}
mDragging = true;
// View, Canvas, WindowManager の取得・生成
final View view = getChildByIndex(mPositionFrom);
final Canvas canvas = new Canvas();
final WindowManager wm = getWindowManager();
//view.setVisibility(View.INVISIBLE);
// ドラッグ対象要素の View を Canvas に描画
mDragBitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
DRAG_BITMAP_CONFIG);
canvas.setBitmap(mDragBitmap);
view.draw(canvas);
// 前回使用した ImageView が残っている場合は除去(念のため?)
if (mDragImageView != null) {
wm.removeView(mDragImageView);
}
// ImageView 用の LayoutParams が未設定の場合は設定する
//if (mLayoutParams == null) {
initLayoutParams();
//}
//updateLayoutParams((int)mActionDownEvent.getX(), (int)mActionDownEvent.getY());
// ImageView を生成し WindowManager に addChild する
mDragImageView = new ImageView(getContext());
mDragImageView.setBackgroundColor(mBitmapBackgroundColor);
mDragImageView.setImageBitmap(mDragBitmap);
wm.addView(mDragImageView, mLayoutParams);
// ドラッグ開始
if (mDragListener != null) {
mPositionFrom = mDragListener.onStartDrag(mPositionFrom);
}
//return duringDrag(mActionDownEvent);
return true;
}
/** ドラッグ処理 */
private boolean duringDrag(MotionEvent event) {
if (!mDragging || mDragImageView == null) {
return false;
}
final int x = (int) event.getX();
final int y = (int) event.getY();
final int height = getHeight();
final int middle = height / 2;
// スクロール速度の決定
final int speed;
final int fastBound = height / 9;
final int slowBound = height / 4;
if (event.getEventTime() - event.getDownTime() < 500) {
// ドラッグの開始から500ミリ秒の間はスクロールしない
speed = 0;
} 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;
} else {
speed = 0;
}
// スクロール処理
if (speed != 0) {
// 横方向はとりあえず考えない
int middlePosition = pointToPosition(0, middle);
if (middlePosition == AdapterView.INVALID_POSITION) {
middlePosition = pointToPosition(0, middle + getDividerHeight()
+ 64);
}
final View middleView = getChildByIndex(middlePosition);
if (middleView != null) {
setSelectionFromTop(middlePosition, middleView.getTop() - speed);
}
}
// ImageView の表示や位置を更新
if (mDragImageView.getHeight() < 0) {
mDragImageView.setVisibility(View.INVISIBLE);
} else {
mDragImageView.setVisibility(View.VISIBLE);
}
updateLayoutParams(x, y);
getWindowManager().updateViewLayout(mDragImageView, mLayoutParams);
if (mDragListener != null) {
mPositionFrom = mDragListener.onDuringDrag(mPositionFrom,
pointToPosition(x, y));
}
return true;
}
/** ドラッグ終了 */
private boolean stopDrag(MotionEvent event, boolean isDrop) {
if (!mDragging) {
return false;
}
if (isDrop && mDragListener != null) {
mDragListener.onStopDrag(mPositionFrom, eventToPosition(event));
}
mDragging = false;
if (mDragImageView != null) {
getWindowManager().removeView(mDragImageView);
mDragImageView = null;
// リサイクルするとたまに死ぬけどタイミング分からない by vvakame
// mDragBitmap.recycle();
mDragBitmap = null;
return true;
}
return false;
}
/** 指定インデックスのView要素を取得する */
private View getChildByIndex(int index) {
return getChildAt(index - getFirstVisiblePosition());
}
/** WindowManager の取得 */
protected WindowManager getWindowManager() {
return (WindowManager) getContext().getSystemService(
Context.WINDOW_SERVICE);
}
/** ImageView 用 LayoutParams の初期化 */
protected void initLayoutParams() {
mLayoutParams = new WindowManager.LayoutParams();
mLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
mLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
mLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
mLayoutParams.format = PixelFormat.TRANSLUCENT;
mLayoutParams.windowAnimations = 0;
mLayoutParams.x = getLeft();
mLayoutParams.y = getTop() + (int)this.mActionDownEventY;
}
/** ImageView 用 LayoutParams の座標情報を更新 */
protected void updateLayoutParams(int x, int y) {
// mLayoutParams.y = getTop() + y - 32;
mLayoutParams.y = getTop() + y;
}
/** ドラッグイベントリスナーインターフェース */
public interface DragListener {
/** ドラッグ開始時の処理 */
public int onStartDrag(int position);
/** ドラッグ中の処理 */
public int onDuringDrag(int positionFrom, int positionTo);
/** ドラッグ終了=ドロップ時の処理 */
public boolean onStopDrag(int positionFrom, int positionTo);
}
/** ドラッグイベントリスナー実装 */
public static class SimpleDragListener implements DragListener {
/** ドラッグ開始時の処理 */
@Override
public int onStartDrag(int position) {
return position;
}
/** ドラッグ中の処理 */
@Override
public int onDuringDrag(int positionFrom, int positionTo) {
return positionFrom;
}
/** ドラッグ終了=ドロップ時の処理 */
@Override
public boolean onStopDrag(int positionFrom, int positionTo) {
return positionFrom != positionTo && positionFrom >= 0
|| positionTo >= 0;
}
}
}