• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
TouchInterceptor(Context context, AttributeSet attrs)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
onInterceptTouchEvent(MotionEvent ev)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      */
myPointToPosition(int x, int y)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 
getItemForPosition(int y)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 
adjustScrollBounds(int y)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      */
unExpandViews(boolean deletion)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      */
doExpansion()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
onTouchEvent(MotionEvent ev)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 
startDragging(Bitmap bm, int x, int y)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 
dragView(int x, int y)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 
stopDragging()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 
setTrashcan(Drawable trash)447     public void setTrashcan(Drawable trash) {
448         mTrashcan = trash;
449         mRemoveMode = TRASH;
450     }
451 
setDragListener(DragListener l)452     public void setDragListener(DragListener l) {
453         mDragListener = l;
454     }
455 
setDropListener(DropListener l)456     public void setDropListener(DropListener l) {
457         mDropListener = l;
458     }
459 
setRemoveListener(RemoveListener l)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