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