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