/* * Copyright (C) 2010 beworx.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.bwx.bequick; import android.content.Context; import android.graphics.Bitmap; import android.graphics.PixelFormat; import android.os.Vibrator; import android.util.AttributeSet; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.ListView; import android.widget.Toast; import com.bwx.bequick.fwk.Setting; /** * This class is based on TouchInterceptor.java implementation * from Android Open Source Project. * * @author sergej@beworx.com */ public class SortableSettingsListView extends ListView { static class Dragger { private final Setting mSetting; private final WindowManager.LayoutParams mWindowParams; private final WindowManager mWindowManager; private final int mDragPointOffset; private final int mCoordOffset; private final int mRowHeight; private Bitmap mBitmap; private ImageView mImageView; public Dragger(Context context, Setting setting, ViewGroup item, MotionEvent ev) { mSetting = setting; int y = (int) ev.getY(); mDragPointOffset = y - item.getTop(); mCoordOffset = ((int)ev.getRawY()) - y; mRowHeight = item.getHeight(); // enable cache // clear cache, otherwise we are going to have problems in donut ;) item.setDrawingCacheEnabled(false); item.setDrawingCacheEnabled(true); // create bitmap Bitmap bitmap = Bitmap.createBitmap(item.getDrawingCache()); mBitmap = bitmap; // create window mWindowParams = new WindowManager.LayoutParams(); WindowManager.LayoutParams params = mWindowParams; params.gravity = Gravity.TOP; params.alpha = 0.65f; params.x = 0; params.y = y - mDragPointOffset + mCoordOffset; params.height = WindowManager.LayoutParams.WRAP_CONTENT; params.width = WindowManager.LayoutParams.WRAP_CONTENT; params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; params.format = PixelFormat.TRANSLUCENT; params.windowAnimations = 0; ImageView imageView = new ImageView(context); int backGroundColor = context.getResources().getColor(android.R.color.black); imageView.setBackgroundColor(backGroundColor); imageView.setImageBitmap(bitmap); // add view mWindowManager = (WindowManager) context.getSystemService("window"); mWindowManager.addView(imageView, params); mImageView = imageView; } int getRowHeight() { return mRowHeight; } void move(int x, int y) { mWindowParams.y = y - mDragPointOffset + mCoordOffset; mWindowManager.updateViewLayout(mImageView, mWindowParams); } void cleanup() { mWindowManager.removeView(mImageView); mImageView.setVisibility(View.INVISIBLE); // hidden window won't draw thus we can recycle image mImageView = null; if (mBitmap != null) { mBitmap.recycle(); mBitmap = null; //Log.d("Dragger", "bitmap recycled"); } } Setting getSetting() { return mSetting; } int getMiddleY(int y) { return y - mDragPointOffset + (mRowHeight / 2); } } //private static final String TAG = "SortableListView"; private static final Setting SETTING_PLACEHOLDER = new Setting(Setting.PLACEHOLDER, R.string.txt_status_unknown); // state private Dragger mDragger; private int mCurrentPos; private LayoutSettingsAdapter mAdapter; private int mBigStep; private int mSmallStep; public SortableSettingsListView(Context context, AttributeSet attrs) { super(context, attrs); float scale = context.getResources().getDisplayMetrics().density; mBigStep = (int) (scale * 20); mSmallStep = (int) (scale * 8); } public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: // get item in the list final int x = (int) ev.getX(); final int y = (int) ev.getY(); int pos = pointToPosition(x, y); // absolute position in the list if (pos == AdapterView.INVALID_POSITION) break; // we clicked on delimiter // check if we should drag final ViewGroup item = (ViewGroup) getChildAt(pos - getFirstVisiblePosition()); View draggerIcon = item.findViewById(R.id.icon); if (draggerIcon == null) break; // this item does not have a dragger icon if (x < draggerIcon.getLeft() - 8) break; // user clicked left of the dragger LayoutSettingsAdapter adapter = mAdapter; if (adapter == null) { adapter = (LayoutSettingsAdapter) getAdapter(); // cache adapter mAdapter = adapter; } // create dragger and start dragging Setting setting = (Setting) adapter.getItem(pos); mDragger = new Dragger(getContext(), setting, item, ev); // replace with placeholder adapter.setItem(pos, SETTING_PLACEHOLDER); mCurrentPos = pos; // update view adapter.getView(pos, item, null); // vibrate Vibrator vibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); if (vibrator != null) vibrator.vibrate(30); return false; } return super.onInterceptTouchEvent(ev); } /** * Swap current setting with a setting under given position * @param pos */ private void swapSettings(int pos) { LayoutSettingsAdapter adapter = mAdapter; int cur = mCurrentPos; // swap settings Setting setting = (Setting) adapter.getItem(pos); // new setting //Log.d(TAG, "over setting: " + setting.getId()); adapter.setItem(pos, SETTING_PLACEHOLDER); adapter.setItem(cur, setting); int firstPos = getFirstVisiblePosition(); // update views adapter.getView(pos, getChildAt(pos - firstPos), null); adapter.getView(cur, getChildAt(cur - firstPos), null); mCurrentPos = pos; } public boolean onTouchEvent(MotionEvent ev) { Dragger dragger = mDragger; if (dragger != null) { // we should handle this event because dragger was created int action = ev.getAction(); switch(action) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: final int x = (int) ev.getX(); final int y = (int) ev.getY(); // move dragger dragger.move(x, y); // check if we have to switch elements int pos = pointToPosition(x, dragger.getMiddleY(y)); // absolute position in the list if (pos == AdapterView.INVALID_POSITION) break; // we move over a delimiter // do not allow to put over very first item if (pos == 0) pos = 1; if (pos != mCurrentPos) { // we have to go through all item is they are not direct siblings int step = pos - mCurrentPos > 0 ? 1 : -1; int tmpPos = mCurrentPos; while(true) { tmpPos += step; swapSettings(tmpPos); //Log.d(TAG, "moving:" + tmpPos); if (tmpPos == pos) break; // exit } } // --------- scroll view --------- final int height = getHeight(); final int border = (int) (height / 3.5f); final int border2 = border / 2; int speed = 0; if (y < border) { // scroll up speed = y < border2 ? -mBigStep : -mSmallStep; } else if (y > height - border) { // scroll down speed = y > height - border2 ? mBigStep : mSmallStep; } if (speed != 0) { int ref = pointToPosition(x, y); View v = getChildAt(ref - getFirstVisiblePosition()); if (v != null) { pos = v.getTop(); setSelectionFromTop(ref, pos - speed); } } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: // drop dragger Setting setting = dragger.getSetting(); // update view mAdapter.setItem(mCurrentPos, setting); //mAdapter.notifyDataSetChanged(); // update view mAdapter.getView(mCurrentPos, getChildAt(mCurrentPos - getFirstVisiblePosition()), null); mCurrentPos = 0; // remove dragger dragger.cleanup(); mDragger = null; // show APN control warning when needed if (Constants.SDK_VERSION < 10 /*2.3.3*/ && (setting.id == Setting.MOBILE_DATA_APN || setting.id == Setting.MOBILE_DATA) && mAdapter.isInVisibleInList(setting)) { Toast.makeText(getContext(), R.string.msg_use_mobile_data_hint, Toast.LENGTH_LONG).show(); } break; } return true; } return super.onTouchEvent(ev); } }