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