TouchInterceptor.java revision add0649249b57e025e9d7d26f1ae16f3c0bfb3ce
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.util.AttributeSet; 26import android.view.GestureDetector; 27import android.view.Gravity; 28import android.view.MotionEvent; 29import android.view.View; 30import android.view.ViewConfiguration; 31import android.view.ViewGroup; 32import android.view.WindowManager; 33import android.view.GestureDetector.SimpleOnGestureListener; 34import android.widget.AdapterView; 35import android.widget.ImageView; 36import android.widget.ListView; 37 38public 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 try { 206 layoutChildren(); // force children to be recreated where needed 207 v = getChildAt(i); 208 } catch (IllegalStateException ex) { 209 // layoutChildren throws this sometimes, presumably because we're 210 // in the process of being torn down but are still getting touch 211 // events 212 } 213 if (v == null) { 214 return; 215 } 216 } 217 ViewGroup.LayoutParams params = v.getLayoutParams(); 218 params.height = mItemHeightNormal; 219 v.setLayoutParams(params); 220 v.setVisibility(View.VISIBLE); 221 } 222 } 223 224 /* Adjust visibility and size to make it appear as though 225 * an item is being dragged around and other items are making 226 * room for it: 227 * If dropping the item would result in it still being in the 228 * same place, then make the dragged listitem's size normal, 229 * but make the item invisible. 230 * Otherwise, if the dragged listitem is still on screen, make 231 * it as small as possible and expand the item below the insert 232 * point. 233 * If the dragged item is not on screen, only expand the item 234 * below the current insertpoint. 235 */ 236 private void doExpansion() { 237 int childnum = mDragPos - getFirstVisiblePosition(); 238 if (mDragPos > mFirstDragPos) { 239 childnum++; 240 } 241 242 View first = getChildAt(mFirstDragPos - getFirstVisiblePosition()); 243 244 for (int i = 0;; i++) { 245 View vv = getChildAt(i); 246 if (vv == null) { 247 break; 248 } 249 int height = mItemHeightNormal; 250 int visibility = View.VISIBLE; 251 if (vv.equals(first)) { 252 // processing the item that is being dragged 253 if (mDragPos == mFirstDragPos) { 254 // hovering over the original location 255 visibility = View.INVISIBLE; 256 } else { 257 // not hovering over it 258 height = 1; 259 } 260 } else if (i == childnum) { 261 if (mDragPos < getCount() - 1) { 262 height = mItemHeightExpanded; 263 } 264 } 265 ViewGroup.LayoutParams params = vv.getLayoutParams(); 266 params.height = height; 267 vv.setLayoutParams(params); 268 vv.setVisibility(visibility); 269 } 270 } 271 272 @Override 273 public boolean onTouchEvent(MotionEvent ev) { 274 if (mGestureDetector != null) { 275 mGestureDetector.onTouchEvent(ev); 276 } 277 if ((mDragListener != null || mDropListener != null) && mDragView != null) { 278 int action = ev.getAction(); 279 switch (action) { 280 case MotionEvent.ACTION_UP: 281 case MotionEvent.ACTION_CANCEL: 282 Rect r = mTempRect; 283 mDragView.getDrawingRect(r); 284 stopDragging(); 285 if (mRemoveMode == SLIDE && ev.getX() > r.right * 3 / 4) { 286 if (mRemoveListener != null) { 287 mRemoveListener.remove(mFirstDragPos); 288 } 289 unExpandViews(true); 290 } else { 291 if (mDropListener != null && mDragPos >= 0 && mDragPos < getCount()) { 292 mDropListener.drop(mFirstDragPos, mDragPos); 293 } 294 unExpandViews(false); 295 } 296 break; 297 298 case MotionEvent.ACTION_DOWN: 299 case MotionEvent.ACTION_MOVE: 300 int x = (int) ev.getX(); 301 int y = (int) ev.getY(); 302 dragView(x, y); 303 int itemnum = getItemForPosition(y); 304 if (itemnum >= 0) { 305 if (action == MotionEvent.ACTION_DOWN || itemnum != mDragPos) { 306 if (mDragListener != null) { 307 mDragListener.drag(mDragPos, itemnum); 308 } 309 mDragPos = itemnum; 310 doExpansion(); 311 } 312 int speed = 0; 313 adjustScrollBounds(y); 314 if (y > mLowerBound) { 315 // scroll the list up a bit 316 speed = y > (mHeight + mLowerBound) / 2 ? 16 : 4; 317 } else if (y < mUpperBound) { 318 // scroll the list down a bit 319 speed = y < mUpperBound / 2 ? -16 : -4; 320 } 321 if (speed != 0) { 322 int ref = pointToPosition(0, mHeight / 2); 323 if (ref == AdapterView.INVALID_POSITION) { 324 //we hit a divider or an invisible view, check somewhere else 325 ref = pointToPosition(0, mHeight / 2 + getDividerHeight() + 64); 326 } 327 View v = getChildAt(ref - getFirstVisiblePosition()); 328 if (v!= null) { 329 int pos = v.getTop(); 330 setSelectionFromTop(ref, pos - speed); 331 } 332 } 333 } 334 break; 335 } 336 return true; 337 } 338 return super.onTouchEvent(ev); 339 } 340 341 private void startDragging(Bitmap bm, int y) { 342 stopDragging(); 343 344 mWindowParams = new WindowManager.LayoutParams(); 345 mWindowParams.gravity = Gravity.TOP; 346 mWindowParams.x = 0; 347 mWindowParams.y = y - mDragPoint + mCoordOffset; 348 349 mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT; 350 mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT; 351 mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 352 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE 353 | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON 354 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 355 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; 356 mWindowParams.format = PixelFormat.TRANSLUCENT; 357 mWindowParams.windowAnimations = 0; 358 359 Context context = getContext(); 360 ImageView v = new ImageView(context); 361 int backGroundColor = context.getResources().getColor(R.color.dragndrop_background); 362 v.setBackgroundColor(backGroundColor); 363 v.setImageBitmap(bm); 364 mDragBitmap = bm; 365 366 mWindowManager = (WindowManager)context.getSystemService("window"); 367 mWindowManager.addView(v, mWindowParams); 368 mDragView = v; 369 } 370 371 private void dragView(int x, int y) { 372 if (mRemoveMode == SLIDE) { 373 float alpha = 1.0f; 374 int width = mDragView.getWidth(); 375 if (x > width / 2) { 376 alpha = ((float)(width - x)) / (width / 2); 377 } 378 mWindowParams.alpha = alpha; 379 } 380 if (mRemoveMode == FLING) { 381 mWindowParams.x = x; 382 } 383 mWindowParams.y = y - mDragPoint + mCoordOffset; 384 mWindowManager.updateViewLayout(mDragView, mWindowParams); 385 } 386 387 private void stopDragging() { 388 if (mDragView != null) { 389 WindowManager wm = (WindowManager)getContext().getSystemService("window"); 390 wm.removeView(mDragView); 391 mDragView.setImageDrawable(null); 392 mDragView = null; 393 } 394 if (mDragBitmap != null) { 395 mDragBitmap.recycle(); 396 mDragBitmap = null; 397 } 398 } 399 400 public void setDragListener(DragListener l) { 401 mDragListener = l; 402 } 403 404 public void setDropListener(DropListener l) { 405 mDropListener = l; 406 } 407 408 public void setRemoveListener(RemoveListener l) { 409 mRemoveListener = l; 410 } 411 412 public interface DragListener { 413 void drag(int from, int to); 414 } 415 public interface DropListener { 416 void drop(int from, int to); 417 } 418 public interface RemoveListener { 419 void remove(int which); 420 } 421} 422