1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.music; 18 19 import android.content.Context; 20 import android.content.SharedPreferences; 21 import android.content.res.Resources; 22 import android.graphics.Bitmap; 23 import android.graphics.PixelFormat; 24 import android.graphics.Rect; 25 import android.util.AttributeSet; 26 import android.view.GestureDetector; 27 import android.view.Gravity; 28 import android.view.MotionEvent; 29 import android.view.View; 30 import android.view.ViewConfiguration; 31 import android.view.ViewGroup; 32 import android.view.WindowManager; 33 import android.view.GestureDetector.SimpleOnGestureListener; 34 import android.widget.AdapterView; 35 import android.widget.ImageView; 36 import android.widget.ListView; 37 38 public class TouchInterceptor extends ListView { 39 40 private ImageView mDragView; 41 private WindowManager mWindowManager; 42 private WindowManager.LayoutParams mWindowParams; 43 private int mDragPos; // which item is being dragged 44 private int mFirstDragPos; // where was the dragged item originally 45 private int mDragPoint; // at what offset inside the item did the user grab it 46 private int mCoordOffset; // the difference between screen coordinates and coordinates in this view 47 private DragListener mDragListener; 48 private DropListener mDropListener; 49 private RemoveListener mRemoveListener; 50 private int mUpperBound; 51 private int mLowerBound; 52 private int mHeight; 53 private GestureDetector mGestureDetector; 54 private static final int FLING = 0; 55 private static final int SLIDE = 1; 56 private int mRemoveMode = -1; 57 private Rect mTempRect = new Rect(); 58 private Bitmap mDragBitmap; 59 private final int mTouchSlop; 60 private int mItemHeightNormal; 61 private int mItemHeightExpanded; 62 private int mItemHeightHalf; 63 64 public TouchInterceptor(Context context, AttributeSet attrs) { 65 super(context, attrs); 66 SharedPreferences pref = context.getSharedPreferences("Music", 3); 67 mRemoveMode = pref.getInt("deletemode", -1); 68 mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 69 Resources res = getResources(); 70 mItemHeightNormal = res.getDimensionPixelSize(R.dimen.normal_height); 71 mItemHeightHalf = mItemHeightNormal / 2; 72 mItemHeightExpanded = res.getDimensionPixelSize(R.dimen.expanded_height); 73 } 74 75 @Override 76 public boolean onInterceptTouchEvent(MotionEvent ev) { 77 if (mRemoveListener != null && mGestureDetector == null) { 78 if (mRemoveMode == FLING) { 79 mGestureDetector = new GestureDetector(getContext(), new SimpleOnGestureListener() { 80 @Override 81 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, 82 float velocityY) { 83 if (mDragView != null) { 84 if (velocityX > 1000) { 85 Rect r = mTempRect; 86 mDragView.getDrawingRect(r); 87 if ( e2.getX() > r.right * 2 / 3) { 88 // fast fling right with release near the right edge of the screen 89 stopDragging(); 90 mRemoveListener.remove(mFirstDragPos); 91 unExpandViews(true); 92 } 93 } 94 // flinging while dragging should have no effect 95 return true; 96 } 97 return false; 98 } 99 }); 100 } 101 } 102 if (mDragListener != null || mDropListener != null) { 103 switch (ev.getAction()) { 104 case MotionEvent.ACTION_DOWN: 105 int x = (int) ev.getX(); 106 int y = (int) ev.getY(); 107 int itemnum = pointToPosition(x, y); 108 if (itemnum == AdapterView.INVALID_POSITION) { 109 break; 110 } 111 ViewGroup item = (ViewGroup) getChildAt(itemnum - getFirstVisiblePosition()); 112 mDragPoint = y - item.getTop(); 113 mCoordOffset = ((int)ev.getRawY()) - y; 114 View dragger = item.findViewById(R.id.icon); 115 Rect r = mTempRect; 116 dragger.getDrawingRect(r); 117 // The dragger icon itself is quite small, so pretend the touch area is bigger 118 if (x < r.right * 2) { 119 item.setDrawingCacheEnabled(true); 120 // Create a copy of the drawing cache so that it does not get recycled 121 // by the framework when the list tries to clean up memory 122 Bitmap bitmap = Bitmap.createBitmap(item.getDrawingCache()); 123 startDragging(bitmap, y); 124 mDragPos = itemnum; 125 mFirstDragPos = mDragPos; 126 mHeight = getHeight(); 127 int touchSlop = mTouchSlop; 128 mUpperBound = Math.min(y - touchSlop, mHeight / 3); 129 mLowerBound = Math.max(y + touchSlop, mHeight * 2 /3); 130 return false; 131 } 132 stopDragging(); 133 break; 134 } 135 } 136 return super.onInterceptTouchEvent(ev); 137 } 138 139 /* 140 * pointToPosition() doesn't consider invisible views, but we 141 * need to, so implement a slightly different version. 142 */ 143 private int myPointToPosition(int x, int y) { 144 145 if (y < 0) { 146 // when dragging off the top of the screen, calculate position 147 // by going back from a visible item 148 int pos = myPointToPosition(x, y + mItemHeightNormal); 149 if (pos > 0) { 150 return pos - 1; 151 } 152 } 153 154 Rect frame = mTempRect; 155 final int count = getChildCount(); 156 for (int i = count - 1; i >= 0; i--) { 157 final View child = getChildAt(i); 158 child.getHitRect(frame); 159 if (frame.contains(x, y)) { 160 return getFirstVisiblePosition() + i; 161 } 162 } 163 return INVALID_POSITION; 164 } 165 166 private int getItemForPosition(int y) { 167 int adjustedy = y - mDragPoint - mItemHeightHalf; 168 int pos = myPointToPosition(0, adjustedy); 169 if (pos >= 0) { 170 if (pos <= mFirstDragPos) { 171 pos += 1; 172 } 173 } else if (adjustedy < 0) { 174 // this shouldn't happen anymore now that myPointToPosition deals 175 // with this situation 176 pos = 0; 177 } 178 return pos; 179 } 180 181 private void adjustScrollBounds(int y) { 182 if (y >= mHeight / 3) { 183 mUpperBound = mHeight / 3; 184 } 185 if (y <= mHeight * 2 / 3) { 186 mLowerBound = mHeight * 2 / 3; 187 } 188 } 189 190 /* 191 * Restore size and visibility for all listitems 192 */ 193 private void unExpandViews(boolean deletion) { 194 for (int i = 0;; i++) { 195 View v = getChildAt(i); 196 if (v == null) { 197 if (deletion) { 198 // HACK force update of mItemCount 199 int position = getFirstVisiblePosition(); 200 int y = getChildAt(0).getTop(); 201 setAdapter(getAdapter()); 202 setSelectionFromTop(position, y); 203 // end hack 204 } 205 layoutChildren(); // force children to be recreated where needed 206 v = getChildAt(i); 207 if (v == null) { 208 break; 209 } 210 } 211 ViewGroup.LayoutParams params = v.getLayoutParams(); 212 params.height = mItemHeightNormal; 213 v.setLayoutParams(params); 214 v.setVisibility(View.VISIBLE); 215 } 216 } 217 218 /* Adjust visibility and size to make it appear as though 219 * an item is being dragged around and other items are making 220 * room for it: 221 * If dropping the item would result in it still being in the 222 * same place, then make the dragged listitem's size normal, 223 * but make the item invisible. 224 * Otherwise, if the dragged listitem is still on screen, make 225 * it as small as possible and expand the item below the insert 226 * point. 227 * If the dragged item is not on screen, only expand the item 228 * below the current insertpoint. 229 */ 230 private void doExpansion() { 231 int childnum = mDragPos - getFirstVisiblePosition(); 232 if (mDragPos > mFirstDragPos) { 233 childnum++; 234 } 235 236 View first = getChildAt(mFirstDragPos - getFirstVisiblePosition()); 237 238 for (int i = 0;; i++) { 239 View vv = getChildAt(i); 240 if (vv == null) { 241 break; 242 } 243 int height = mItemHeightNormal; 244 int visibility = View.VISIBLE; 245 if (vv.equals(first)) { 246 // processing the item that is being dragged 247 if (mDragPos == mFirstDragPos) { 248 // hovering over the original location 249 visibility = View.INVISIBLE; 250 } else { 251 // not hovering over it 252 height = 1; 253 } 254 } else if (i == childnum) { 255 if (mDragPos < getCount() - 1) { 256 height = mItemHeightExpanded; 257 } 258 } 259 ViewGroup.LayoutParams params = vv.getLayoutParams(); 260 params.height = height; 261 vv.setLayoutParams(params); 262 vv.setVisibility(visibility); 263 } 264 } 265 266 @Override 267 public boolean onTouchEvent(MotionEvent ev) { 268 if (mGestureDetector != null) { 269 mGestureDetector.onTouchEvent(ev); 270 } 271 if ((mDragListener != null || mDropListener != null) && mDragView != null) { 272 int action = ev.getAction(); 273 switch (action) { 274 case MotionEvent.ACTION_UP: 275 case MotionEvent.ACTION_CANCEL: 276 Rect r = mTempRect; 277 mDragView.getDrawingRect(r); 278 stopDragging(); 279 if (mRemoveMode == SLIDE && ev.getX() > r.right * 3 / 4) { 280 if (mRemoveListener != null) { 281 mRemoveListener.remove(mFirstDragPos); 282 } 283 unExpandViews(true); 284 } else { 285 if (mDropListener != null && mDragPos >= 0 && mDragPos < getCount()) { 286 mDropListener.drop(mFirstDragPos, mDragPos); 287 } 288 unExpandViews(false); 289 } 290 break; 291 292 case MotionEvent.ACTION_DOWN: 293 case MotionEvent.ACTION_MOVE: 294 int x = (int) ev.getX(); 295 int y = (int) ev.getY(); 296 dragView(x, y); 297 int itemnum = getItemForPosition(y); 298 if (itemnum >= 0) { 299 if (action == MotionEvent.ACTION_DOWN || itemnum != mDragPos) { 300 if (mDragListener != null) { 301 mDragListener.drag(mDragPos, itemnum); 302 } 303 mDragPos = itemnum; 304 doExpansion(); 305 } 306 int speed = 0; 307 adjustScrollBounds(y); 308 if (y > mLowerBound) { 309 // scroll the list up a bit 310 speed = y > (mHeight + mLowerBound) / 2 ? 16 : 4; 311 } else if (y < mUpperBound) { 312 // scroll the list down a bit 313 speed = y < mUpperBound / 2 ? -16 : -4; 314 } 315 if (speed != 0) { 316 int ref = pointToPosition(0, mHeight / 2); 317 if (ref == AdapterView.INVALID_POSITION) { 318 //we hit a divider or an invisible view, check somewhere else 319 ref = pointToPosition(0, mHeight / 2 + getDividerHeight() + 64); 320 } 321 View v = getChildAt(ref - getFirstVisiblePosition()); 322 if (v!= null) { 323 int pos = v.getTop(); 324 setSelectionFromTop(ref, pos - speed); 325 } 326 } 327 } 328 break; 329 } 330 return true; 331 } 332 return super.onTouchEvent(ev); 333 } 334 335 private void startDragging(Bitmap bm, int y) { 336 stopDragging(); 337 338 mWindowParams = new WindowManager.LayoutParams(); 339 mWindowParams.gravity = Gravity.TOP; 340 mWindowParams.x = 0; 341 mWindowParams.y = y - mDragPoint + mCoordOffset; 342 343 mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT; 344 mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT; 345 mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 346 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE 347 | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON 348 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 349 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; 350 mWindowParams.format = PixelFormat.TRANSLUCENT; 351 mWindowParams.windowAnimations = 0; 352 353 Context context = getContext(); 354 ImageView v = new ImageView(context); 355 int backGroundColor = context.getResources().getColor(R.color.dragndrop_background); 356 v.setBackgroundColor(backGroundColor); 357 v.setImageBitmap(bm); 358 mDragBitmap = bm; 359 360 mWindowManager = (WindowManager)context.getSystemService("window"); 361 mWindowManager.addView(v, mWindowParams); 362 mDragView = v; 363 } 364 365 private void dragView(int x, int y) { 366 if (mRemoveMode == SLIDE) { 367 float alpha = 1.0f; 368 int width = mDragView.getWidth(); 369 if (x > width / 2) { 370 alpha = ((float)(width - x)) / (width / 2); 371 } 372 mWindowParams.alpha = alpha; 373 } 374 if (mRemoveMode == FLING) { 375 mWindowParams.x = x; 376 } 377 mWindowParams.y = y - mDragPoint + mCoordOffset; 378 mWindowManager.updateViewLayout(mDragView, mWindowParams); 379 } 380 381 private void stopDragging() { 382 if (mDragView != null) { 383 WindowManager wm = (WindowManager)getContext().getSystemService("window"); 384 wm.removeView(mDragView); 385 mDragView.setImageDrawable(null); 386 mDragView = null; 387 } 388 if (mDragBitmap != null) { 389 mDragBitmap.recycle(); 390 mDragBitmap = null; 391 } 392 } 393 394 public void setDragListener(DragListener l) { 395 mDragListener = l; 396 } 397 398 public void setDropListener(DropListener l) { 399 mDropListener = l; 400 } 401 402 public void setRemoveListener(RemoveListener l) { 403 mRemoveListener = l; 404 } 405 406 public interface DragListener { 407 void drag(int from, int to); 408 } 409 public interface DropListener { 410 void drop(int from, int to); 411 } 412 public interface RemoveListener { 413 void remove(int which); 414 } 415 } 416