TouchInterceptor.java revision b5e252efe636571789d8ea7d9fe9800ad88260cc
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 63 public TouchInterceptor(Context context, AttributeSet attrs) { 64 super(context, attrs); 65 SharedPreferences pref = context.getSharedPreferences("Music", 3); 66 mRemoveMode = pref.getInt("deletemode", -1); 67 mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 68 Resources res = getResources(); 69 mItemHeightNormal = res.getDimensionPixelSize(R.dimen.normal_height); 70 mItemHeightExpanded = res.getDimensionPixelSize(R.dimen.expanded_height); 71 } 72 73 @Override 74 public boolean onInterceptTouchEvent(MotionEvent ev) { 75 if (mRemoveListener != null && mGestureDetector == null) { 76 if (mRemoveMode == FLING) { 77 mGestureDetector = new GestureDetector(getContext(), new SimpleOnGestureListener() { 78 @Override 79 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, 80 float velocityY) { 81 if (mDragView != null) { 82 if (velocityX > 1000) { 83 Rect r = mTempRect; 84 mDragView.getDrawingRect(r); 85 if ( e2.getX() > r.right * 2 / 3) { 86 // fast fling right with release near the right edge of the screen 87 stopDragging(); 88 mRemoveListener.remove(mFirstDragPos); 89 unExpandViews(true); 90 } 91 } 92 // flinging while dragging should have no effect 93 return true; 94 } 95 return false; 96 } 97 }); 98 } 99 } 100 if (mDragListener != null || mDropListener != null) { 101 switch (ev.getAction()) { 102 case MotionEvent.ACTION_DOWN: 103 int x = (int) ev.getX(); 104 int y = (int) ev.getY(); 105 int itemnum = pointToPosition(x, y); 106 if (itemnum == AdapterView.INVALID_POSITION) { 107 break; 108 } 109 ViewGroup item = (ViewGroup) getChildAt(itemnum - getFirstVisiblePosition()); 110 mDragPoint = y - item.getTop(); 111 mCoordOffset = ((int)ev.getRawY()) - y; 112 View dragger = item.findViewById(R.id.icon); 113 Rect r = mTempRect; 114 dragger.getDrawingRect(r); 115 // The dragger icon itself is quite small, so pretend the touch area is bigger 116 if (x < r.right * 2) { 117 item.setDrawingCacheEnabled(true); 118 // Create a copy of the drawing cache so that it does not get recycled 119 // by the framework when the list tries to clean up memory 120 Bitmap bitmap = Bitmap.createBitmap(item.getDrawingCache()); 121 startDragging(bitmap, y); 122 mDragPos = itemnum; 123 mFirstDragPos = mDragPos; 124 mHeight = getHeight(); 125 int touchSlop = mTouchSlop; 126 mUpperBound = Math.min(y - touchSlop, mHeight / 3); 127 mLowerBound = Math.max(y + touchSlop, mHeight * 2 /3); 128 return false; 129 } 130 mDragView = null; 131 break; 132 } 133 } 134 return super.onInterceptTouchEvent(ev); 135 } 136 137 /* 138 * pointToPosition() doesn't consider invisible views, but we 139 * need to, so implement a slightly different version. 140 */ 141 private int myPointToPosition(int x, int y) { 142 Rect frame = mTempRect; 143 final int count = getChildCount(); 144 for (int i = count - 1; i >= 0; i--) { 145 final View child = getChildAt(i); 146 child.getHitRect(frame); 147 if (frame.contains(x, y)) { 148 return getFirstVisiblePosition() + i; 149 } 150 } 151 return INVALID_POSITION; 152 } 153 154 private int getItemForPosition(int y) { 155 int adjustedy = y - mDragPoint - 32; 156 int pos = myPointToPosition(0, adjustedy); 157 if (pos >= 0) { 158 if (pos <= mFirstDragPos) { 159 pos += 1; 160 } 161 } else if (adjustedy < 0) { 162 pos = 0; 163 } 164 return pos; 165 } 166 167 private void adjustScrollBounds(int y) { 168 if (y >= mHeight / 3) { 169 mUpperBound = mHeight / 3; 170 } 171 if (y <= mHeight * 2 / 3) { 172 mLowerBound = mHeight * 2 / 3; 173 } 174 } 175 176 /* 177 * Restore size and visibility for all listitems 178 */ 179 private void unExpandViews(boolean deletion) { 180 for (int i = 0;; i++) { 181 View v = getChildAt(i); 182 if (v == null) { 183 if (deletion) { 184 // HACK force update of mItemCount 185 int position = getFirstVisiblePosition(); 186 int y = getChildAt(0).getTop(); 187 setAdapter(getAdapter()); 188 setSelectionFromTop(position, y); 189 // end hack 190 } 191 layoutChildren(); // force children to be recreated where needed 192 v = getChildAt(i); 193 if (v == null) { 194 break; 195 } 196 } 197 ViewGroup.LayoutParams params = v.getLayoutParams(); 198 params.height = mItemHeightNormal; 199 v.setLayoutParams(params); 200 v.setVisibility(View.VISIBLE); 201 } 202 } 203 204 /* Adjust visibility and size to make it appear as though 205 * an item is being dragged around and other items are making 206 * room for it: 207 * If dropping the item would result in it still being in the 208 * same place, then make the dragged listitem's size normal, 209 * but make the item invisible. 210 * Otherwise, if the dragged listitem is still on screen, make 211 * it as small as possible and expand the item below the insert 212 * point. 213 * If the dragged item is not on screen, only expand the item 214 * below the current insertpoint. 215 */ 216 private void doExpansion() { 217 int childnum = mDragPos - getFirstVisiblePosition(); 218 if (mDragPos > mFirstDragPos) { 219 childnum++; 220 } 221 222 View first = getChildAt(mFirstDragPos - getFirstVisiblePosition()); 223 224 for (int i = 0;; i++) { 225 View vv = getChildAt(i); 226 if (vv == null) { 227 break; 228 } 229 int height = mItemHeightNormal; 230 int visibility = View.VISIBLE; 231 if (vv.equals(first)) { 232 // processing the item that is being dragged 233 if (mDragPos == mFirstDragPos) { 234 // hovering over the original location 235 visibility = View.INVISIBLE; 236 } else { 237 // not hovering over it 238 height = 1; 239 } 240 } else if (i == childnum) { 241 if (mDragPos < getCount() - 1) { 242 height = mItemHeightExpanded; 243 } 244 } 245 ViewGroup.LayoutParams params = vv.getLayoutParams(); 246 params.height = height; 247 vv.setLayoutParams(params); 248 vv.setVisibility(visibility); 249 } 250 } 251 252 @Override 253 public boolean onTouchEvent(MotionEvent ev) { 254 if (mGestureDetector != null) { 255 mGestureDetector.onTouchEvent(ev); 256 } 257 if ((mDragListener != null || mDropListener != null) && mDragView != null) { 258 int action = ev.getAction(); 259 switch (action) { 260 case MotionEvent.ACTION_UP: 261 case MotionEvent.ACTION_CANCEL: 262 Rect r = mTempRect; 263 mDragView.getDrawingRect(r); 264 stopDragging(); 265 if (mRemoveMode == SLIDE && ev.getX() > r.right * 3 / 4) { 266 if (mRemoveListener != null) { 267 mRemoveListener.remove(mFirstDragPos); 268 } 269 unExpandViews(true); 270 } else { 271 if (mDropListener != null && mDragPos >= 0 && mDragPos < getCount()) { 272 mDropListener.drop(mFirstDragPos, mDragPos); 273 } 274 unExpandViews(false); 275 } 276 break; 277 278 case MotionEvent.ACTION_DOWN: 279 case MotionEvent.ACTION_MOVE: 280 int x = (int) ev.getX(); 281 int y = (int) ev.getY(); 282 dragView(x, y); 283 int itemnum = getItemForPosition(y); 284 if (itemnum >= 0) { 285 if (action == MotionEvent.ACTION_DOWN || itemnum != mDragPos) { 286 if (mDragListener != null) { 287 mDragListener.drag(mDragPos, itemnum); 288 } 289 mDragPos = itemnum; 290 doExpansion(); 291 } 292 int speed = 0; 293 adjustScrollBounds(y); 294 if (y > mLowerBound) { 295 // scroll the list up a bit 296 speed = y > (mHeight + mLowerBound) / 2 ? 16 : 4; 297 } else if (y < mUpperBound) { 298 // scroll the list down a bit 299 speed = y < mUpperBound / 2 ? -16 : -4; 300 } 301 if (speed != 0) { 302 int ref = pointToPosition(0, mHeight / 2); 303 if (ref == AdapterView.INVALID_POSITION) { 304 //we hit a divider or an invisible view, check somewhere else 305 ref = pointToPosition(0, mHeight / 2 + getDividerHeight() + 64); 306 } 307 View v = getChildAt(ref - getFirstVisiblePosition()); 308 if (v!= null) { 309 int pos = v.getTop(); 310 setSelectionFromTop(ref, pos - speed); 311 } 312 } 313 } 314 break; 315 } 316 return true; 317 } 318 return super.onTouchEvent(ev); 319 } 320 321 private void startDragging(Bitmap bm, int y) { 322 stopDragging(); 323 324 mWindowParams = new WindowManager.LayoutParams(); 325 mWindowParams.gravity = Gravity.TOP; 326 mWindowParams.x = 0; 327 mWindowParams.y = y - mDragPoint + mCoordOffset; 328 329 mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT; 330 mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT; 331 mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 332 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE 333 | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON 334 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; 335 mWindowParams.format = PixelFormat.TRANSLUCENT; 336 mWindowParams.windowAnimations = 0; 337 338 ImageView v = new ImageView(mContext); 339 int backGroundColor = mContext.getResources().getColor(R.color.dragndrop_background); 340 v.setBackgroundColor(backGroundColor); 341 v.setImageBitmap(bm); 342 mDragBitmap = bm; 343 344 mWindowManager = (WindowManager)mContext.getSystemService("window"); 345 mWindowManager.addView(v, mWindowParams); 346 mDragView = v; 347 } 348 349 private void dragView(int x, int y) { 350 if (mRemoveMode == SLIDE) { 351 float alpha = 1.0f; 352 int width = mDragView.getWidth(); 353 if (x > width / 2) { 354 alpha = ((float)(width - x)) / (width / 2); 355 } 356 mWindowParams.alpha = alpha; 357 } 358 mWindowParams.y = y - mDragPoint + mCoordOffset; 359 mWindowManager.updateViewLayout(mDragView, mWindowParams); 360 } 361 362 private void stopDragging() { 363 if (mDragView != null) { 364 WindowManager wm = (WindowManager)mContext.getSystemService("window"); 365 wm.removeView(mDragView); 366 mDragView.setImageDrawable(null); 367 mDragView = null; 368 } 369 if (mDragBitmap != null) { 370 mDragBitmap.recycle(); 371 mDragBitmap = null; 372 } 373 } 374 375 public void setDragListener(DragListener l) { 376 mDragListener = l; 377 } 378 379 public void setDropListener(DropListener l) { 380 mDropListener = l; 381 } 382 383 public void setRemoveListener(RemoveListener l) { 384 mRemoveListener = l; 385 } 386 387 public interface DragListener { 388 void drag(int from, int to); 389 } 390 public interface DropListener { 391 void drop(int from, int to); 392 } 393 public interface RemoveListener { 394 void remove(int which); 395 } 396} 397