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