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.graphics.drawable.Drawable; 26 import android.graphics.drawable.LevelListDrawable; 27 import android.util.AttributeSet; 28 import android.util.Log; 29 import android.view.GestureDetector; 30 import android.view.Gravity; 31 import android.view.MotionEvent; 32 import android.view.View; 33 import android.view.ViewConfiguration; 34 import android.view.ViewGroup; 35 import android.view.WindowManager; 36 import android.view.GestureDetector.SimpleOnGestureListener; 37 import android.widget.AdapterView; 38 import android.widget.ImageView; 39 import android.widget.ListView; 40 41 public class TouchInterceptor extends ListView { 42 private ImageView mDragView; 43 private WindowManager mWindowManager; 44 private WindowManager.LayoutParams mWindowParams; 45 /** 46 * At which position is the item currently being dragged. Note that this 47 * takes in to account header items. 48 */ 49 private int mDragPos; 50 /** 51 * At which position was the item being dragged originally 52 */ 53 private int mSrcDragPos; 54 private int mDragPointX; // at what x offset inside the item did the user grab it 55 private int mDragPointY; // at what y offset inside the item did the user grab it 56 private int mXOffset; // the difference between screen coordinates and coordinates in this view 57 private int mYOffset; // the difference between screen coordinates and coordinates in this view 58 private DragListener mDragListener; 59 private DropListener mDropListener; 60 private RemoveListener mRemoveListener; 61 private int mUpperBound; 62 private int mLowerBound; 63 private int mHeight; 64 private GestureDetector mGestureDetector; 65 private static final int FLING = 0; 66 private static final int SLIDE = 1; 67 private static final int TRASH = 2; 68 private int mRemoveMode = -1; 69 private Rect mTempRect = new Rect(); 70 private Bitmap mDragBitmap; 71 private final int mTouchSlop; 72 private int mItemHeightNormal; 73 private int mItemHeightExpanded; 74 private int mItemHeightHalf; 75 private Drawable mTrashcan; 76 77 public TouchInterceptor(Context context, AttributeSet attrs) { 78 super(context, attrs); 79 SharedPreferences pref = context.getSharedPreferences("Music", 0); 80 mRemoveMode = pref.getInt("deletemode", -1); 81 mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 82 Resources res = getResources(); 83 mItemHeightNormal = res.getDimensionPixelSize(R.dimen.normal_height); 84 mItemHeightHalf = mItemHeightNormal / 2; 85 mItemHeightExpanded = res.getDimensionPixelSize(R.dimen.expanded_height); 86 } 87 88 @Override 89 public boolean onInterceptTouchEvent(MotionEvent ev) { 90 if (mRemoveListener != null && mGestureDetector == null) { 91 if (mRemoveMode == FLING) { 92 mGestureDetector = new GestureDetector(getContext(), new SimpleOnGestureListener() { 93 @Override 94 public boolean onFling( 95 MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 96 if (mDragView != null) { 97 if (velocityX > 1000) { 98 Rect r = mTempRect; 99 mDragView.getDrawingRect(r); 100 if (e2.getX() > r.right * 2 / 3) { 101 // fast fling right with release near the right edge of the 102 // screen 103 stopDragging(); 104 mRemoveListener.remove(mSrcDragPos); 105 unExpandViews(true); 106 } 107 } 108 // flinging while dragging should have no effect 109 return true; 110 } 111 return false; 112 } 113 }); 114 } 115 } 116 if (mDragListener != null || mDropListener != null) { 117 switch (ev.getAction()) { 118 case MotionEvent.ACTION_DOWN: 119 int x = (int) ev.getX(); 120 int y = (int) ev.getY(); 121 int itemnum = pointToPosition(x, y); 122 if (itemnum == AdapterView.INVALID_POSITION) { 123 break; 124 } 125 ViewGroup item = (ViewGroup) getChildAt(itemnum - getFirstVisiblePosition()); 126 mDragPointX = x - item.getLeft(); 127 mDragPointY = y - item.getTop(); 128 mXOffset = ((int) ev.getRawX()) - x; 129 mYOffset = ((int) ev.getRawY()) - y; 130 // The left side of the item is the grabber for dragging the item 131 if (x < 64) { 132 item.setDrawingCacheEnabled(true); 133 // Create a copy of the drawing cache so that it does not get recycled 134 // by the framework when the list tries to clean up memory 135 Bitmap bitmap = Bitmap.createBitmap(item.getDrawingCache()); 136 startDragging(bitmap, x, y); 137 mDragPos = itemnum; 138 mSrcDragPos = mDragPos; 139 mHeight = getHeight(); 140 int touchSlop = mTouchSlop; 141 mUpperBound = Math.min(y - touchSlop, mHeight / 3); 142 mLowerBound = Math.max(y + touchSlop, mHeight * 2 / 3); 143 return false; 144 } 145 stopDragging(); 146 break; 147 } 148 } 149 return super.onInterceptTouchEvent(ev); 150 } 151 152 /* 153 * pointToPosition() doesn't consider invisible views, but we 154 * need to, so implement a slightly different version. 155 */ 156 private int myPointToPosition(int x, int y) { 157 if (y < 0) { 158 // when dragging off the top of the screen, calculate position 159 // by going back from a visible item 160 int pos = myPointToPosition(x, y + mItemHeightNormal); 161 if (pos > 0) { 162 return pos - 1; 163 } 164 } 165 166 Rect frame = mTempRect; 167 final int count = getChildCount(); 168 for (int i = count - 1; i >= 0; i--) { 169 final View child = getChildAt(i); 170 child.getHitRect(frame); 171 if (frame.contains(x, y)) { 172 return getFirstVisiblePosition() + i; 173 } 174 } 175 return INVALID_POSITION; 176 } 177 178 private int getItemForPosition(int y) { 179 int adjustedy = y - mDragPointY - mItemHeightHalf; 180 int pos = myPointToPosition(0, adjustedy); 181 if (pos >= 0) { 182 if (pos <= mSrcDragPos) { 183 pos += 1; 184 } 185 } else if (adjustedy < 0) { 186 // this shouldn't happen anymore now that myPointToPosition deals 187 // with this situation 188 pos = 0; 189 } 190 return pos; 191 } 192 193 private void adjustScrollBounds(int y) { 194 if (y >= mHeight / 3) { 195 mUpperBound = mHeight / 3; 196 } 197 if (y <= mHeight * 2 / 3) { 198 mLowerBound = mHeight * 2 / 3; 199 } 200 } 201 202 /* 203 * Restore size and visibility for all listitems 204 */ 205 private void unExpandViews(boolean deletion) { 206 for (int i = 0;; i++) { 207 View v = getChildAt(i); 208 if (v == null) { 209 if (deletion) { 210 // HACK force update of mItemCount 211 int position = getFirstVisiblePosition(); 212 int y = getChildAt(0).getTop(); 213 setAdapter(getAdapter()); 214 setSelectionFromTop(position, y); 215 // end hack 216 } 217 try { 218 layoutChildren(); // force children to be recreated where needed 219 v = getChildAt(i); 220 } catch (IllegalStateException ex) { 221 // layoutChildren throws this sometimes, presumably because we're 222 // in the process of being torn down but are still getting touch 223 // events 224 } 225 if (v == null) { 226 return; 227 } 228 } 229 ViewGroup.LayoutParams params = v.getLayoutParams(); 230 params.height = mItemHeightNormal; 231 v.setLayoutParams(params); 232 v.setVisibility(View.VISIBLE); 233 } 234 } 235 236 /* Adjust visibility and size to make it appear as though 237 * an item is being dragged around and other items are making 238 * room for it: 239 * If dropping the item would result in it still being in the 240 * same place, then make the dragged listitem's size normal, 241 * but make the item invisible. 242 * Otherwise, if the dragged listitem is still on screen, make 243 * it as small as possible and expand the item below the insert 244 * point. 245 * If the dragged item is not on screen, only expand the item 246 * below the current insertpoint. 247 */ 248 private void doExpansion() { 249 int childnum = mDragPos - getFirstVisiblePosition(); 250 if (mDragPos > mSrcDragPos) { 251 childnum++; 252 } 253 int numheaders = getHeaderViewsCount(); 254 255 View first = getChildAt(mSrcDragPos - getFirstVisiblePosition()); 256 for (int i = 0;; i++) { 257 View vv = getChildAt(i); 258 if (vv == null) { 259 break; 260 } 261 262 int height = mItemHeightNormal; 263 int visibility = View.VISIBLE; 264 if (mDragPos < numheaders && i == numheaders) { 265 // dragging on top of the header item, so adjust the item below 266 // instead 267 if (vv.equals(first)) { 268 visibility = View.INVISIBLE; 269 } else { 270 height = mItemHeightExpanded; 271 } 272 } else if (vv.equals(first)) { 273 // processing the item that is being dragged 274 if (mDragPos == mSrcDragPos || getPositionForView(vv) == getCount() - 1) { 275 // hovering over the original location 276 visibility = View.INVISIBLE; 277 } else { 278 // not hovering over it 279 // Ideally the item would be completely gone, but neither 280 // setting its size to 0 nor settings visibility to GONE 281 // has the desired effect. 282 height = 1; 283 } 284 } else if (i == childnum) { 285 if (mDragPos >= numheaders && mDragPos < getCount() - 1) { 286 height = mItemHeightExpanded; 287 } 288 } 289 ViewGroup.LayoutParams params = vv.getLayoutParams(); 290 params.height = height; 291 vv.setLayoutParams(params); 292 vv.setVisibility(visibility); 293 } 294 } 295 296 @Override 297 public boolean onTouchEvent(MotionEvent ev) { 298 if (mGestureDetector != null) { 299 mGestureDetector.onTouchEvent(ev); 300 } 301 if ((mDragListener != null || mDropListener != null) && mDragView != null) { 302 int action = ev.getAction(); 303 switch (action) { 304 case MotionEvent.ACTION_UP: 305 case MotionEvent.ACTION_CANCEL: 306 Rect r = mTempRect; 307 mDragView.getDrawingRect(r); 308 stopDragging(); 309 if (mRemoveMode == SLIDE && ev.getX() > r.right * 3 / 4) { 310 if (mRemoveListener != null) { 311 mRemoveListener.remove(mSrcDragPos); 312 } 313 unExpandViews(true); 314 } else { 315 if (mDropListener != null && mDragPos >= 0 && mDragPos < getCount()) { 316 mDropListener.drop(mSrcDragPos, mDragPos); 317 } 318 unExpandViews(false); 319 } 320 break; 321 322 case MotionEvent.ACTION_DOWN: 323 case MotionEvent.ACTION_MOVE: 324 int x = (int) ev.getX(); 325 int y = (int) ev.getY(); 326 dragView(x, y); 327 int itemnum = getItemForPosition(y); 328 if (itemnum >= 0) { 329 if (action == MotionEvent.ACTION_DOWN || itemnum != mDragPos) { 330 if (mDragListener != null) { 331 mDragListener.drag(mDragPos, itemnum); 332 } 333 mDragPos = itemnum; 334 doExpansion(); 335 } 336 int speed = 0; 337 adjustScrollBounds(y); 338 if (y > mLowerBound) { 339 // scroll the list up a bit 340 if (getLastVisiblePosition() < getCount() - 1) { 341 speed = y > (mHeight + mLowerBound) / 2 ? 16 : 4; 342 } else { 343 speed = 1; 344 } 345 } else if (y < mUpperBound) { 346 // scroll the list down a bit 347 speed = y < mUpperBound / 2 ? -16 : -4; 348 if (getFirstVisiblePosition() == 0 349 && getChildAt(0).getTop() >= getPaddingTop()) { 350 // if we're already at the top, don't try to scroll, because 351 // it causes the framework to do some extra drawing that messes 352 // up our animation 353 speed = 0; 354 } 355 } 356 if (speed != 0) { 357 smoothScrollBy(speed, 30); 358 } 359 } 360 break; 361 } 362 return true; 363 } 364 return super.onTouchEvent(ev); 365 } 366 367 private void startDragging(Bitmap bm, int x, int y) { 368 stopDragging(); 369 370 mWindowParams = new WindowManager.LayoutParams(); 371 mWindowParams.gravity = Gravity.TOP | Gravity.LEFT; 372 mWindowParams.x = x - mDragPointX + mXOffset; 373 mWindowParams.y = y - mDragPointY + mYOffset; 374 375 mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT; 376 mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT; 377 mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 378 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE 379 | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON 380 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 381 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; 382 mWindowParams.format = PixelFormat.TRANSLUCENT; 383 mWindowParams.windowAnimations = 0; 384 385 Context context = getContext(); 386 ImageView v = new ImageView(context); 387 // int backGroundColor = context.getResources().getColor(R.color.dragndrop_background); 388 // v.setBackgroundColor(backGroundColor); 389 v.setBackgroundResource(R.drawable.playlist_tile_drag); 390 v.setPadding(0, 0, 0, 0); 391 v.setImageBitmap(bm); 392 mDragBitmap = bm; 393 394 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 395 mWindowManager.addView(v, mWindowParams); 396 mDragView = v; 397 } 398 399 private void dragView(int x, int y) { 400 if (mRemoveMode == SLIDE) { 401 float alpha = 1.0f; 402 int width = mDragView.getWidth(); 403 if (x > width / 2) { 404 alpha = ((float) (width - x)) / (width / 2); 405 } 406 mWindowParams.alpha = alpha; 407 } 408 409 if (mRemoveMode == FLING || mRemoveMode == TRASH) { 410 mWindowParams.x = x - mDragPointX + mXOffset; 411 } else { 412 mWindowParams.x = 0; 413 } 414 mWindowParams.y = y - mDragPointY + mYOffset; 415 mWindowManager.updateViewLayout(mDragView, mWindowParams); 416 417 if (mTrashcan != null) { 418 int width = mDragView.getWidth(); 419 if (y > getHeight() * 3 / 4) { 420 mTrashcan.setLevel(2); 421 } else if (width > 0 && x > width / 4) { 422 mTrashcan.setLevel(1); 423 } else { 424 mTrashcan.setLevel(0); 425 } 426 } 427 } 428 429 private void stopDragging() { 430 if (mDragView != null) { 431 mDragView.setVisibility(GONE); 432 WindowManager wm = 433 (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); 434 wm.removeView(mDragView); 435 mDragView.setImageDrawable(null); 436 mDragView = null; 437 } 438 if (mDragBitmap != null) { 439 mDragBitmap.recycle(); 440 mDragBitmap = null; 441 } 442 if (mTrashcan != null) { 443 mTrashcan.setLevel(0); 444 } 445 } 446 447 public void setTrashcan(Drawable trash) { 448 mTrashcan = trash; 449 mRemoveMode = TRASH; 450 } 451 452 public void setDragListener(DragListener l) { 453 mDragListener = l; 454 } 455 456 public void setDropListener(DropListener l) { 457 mDropListener = l; 458 } 459 460 public void setRemoveListener(RemoveListener l) { 461 mRemoveListener = l; 462 } 463 464 public interface DragListener { void drag(int from, int to); } 465 public interface DropListener { void drop(int from, int to); } 466 public interface RemoveListener { void remove(int which); } 467 } 468