Home | History | Annotate | Download | only in music
      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.util.AttributeSet;
     26 import android.view.GestureDetector;
     27 import android.view.Gravity;
     28 import android.view.MotionEvent;
     29 import android.view.View;
     30 import android.view.ViewConfiguration;
     31 import android.view.ViewGroup;
     32 import android.view.WindowManager;
     33 import android.view.GestureDetector.SimpleOnGestureListener;
     34 import android.widget.AdapterView;
     35 import android.widget.ImageView;
     36 import android.widget.ListView;
     37 
     38 public 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                 layoutChildren(); // force children to be recreated where needed
    206                 v = getChildAt(i);
    207                 if (v == null) {
    208                     break;
    209                 }
    210             }
    211             ViewGroup.LayoutParams params = v.getLayoutParams();
    212             params.height = mItemHeightNormal;
    213             v.setLayoutParams(params);
    214             v.setVisibility(View.VISIBLE);
    215         }
    216     }
    217 
    218     /* Adjust visibility and size to make it appear as though
    219      * an item is being dragged around and other items are making
    220      * room for it:
    221      * If dropping the item would result in it still being in the
    222      * same place, then make the dragged listitem's size normal,
    223      * but make the item invisible.
    224      * Otherwise, if the dragged listitem is still on screen, make
    225      * it as small as possible and expand the item below the insert
    226      * point.
    227      * If the dragged item is not on screen, only expand the item
    228      * below the current insertpoint.
    229      */
    230     private void doExpansion() {
    231         int childnum = mDragPos - getFirstVisiblePosition();
    232         if (mDragPos > mFirstDragPos) {
    233             childnum++;
    234         }
    235 
    236         View first = getChildAt(mFirstDragPos - getFirstVisiblePosition());
    237 
    238         for (int i = 0;; i++) {
    239             View vv = getChildAt(i);
    240             if (vv == null) {
    241                 break;
    242             }
    243             int height = mItemHeightNormal;
    244             int visibility = View.VISIBLE;
    245             if (vv.equals(first)) {
    246                 // processing the item that is being dragged
    247                 if (mDragPos == mFirstDragPos) {
    248                     // hovering over the original location
    249                     visibility = View.INVISIBLE;
    250                 } else {
    251                     // not hovering over it
    252                     height = 1;
    253                 }
    254             } else if (i == childnum) {
    255                 if (mDragPos < getCount() - 1) {
    256                     height = mItemHeightExpanded;
    257                 }
    258             }
    259             ViewGroup.LayoutParams params = vv.getLayoutParams();
    260             params.height = height;
    261             vv.setLayoutParams(params);
    262             vv.setVisibility(visibility);
    263         }
    264     }
    265 
    266     @Override
    267     public boolean onTouchEvent(MotionEvent ev) {
    268         if (mGestureDetector != null) {
    269             mGestureDetector.onTouchEvent(ev);
    270         }
    271         if ((mDragListener != null || mDropListener != null) && mDragView != null) {
    272             int action = ev.getAction();
    273             switch (action) {
    274                 case MotionEvent.ACTION_UP:
    275                 case MotionEvent.ACTION_CANCEL:
    276                     Rect r = mTempRect;
    277                     mDragView.getDrawingRect(r);
    278                     stopDragging();
    279                     if (mRemoveMode == SLIDE && ev.getX() > r.right * 3 / 4) {
    280                         if (mRemoveListener != null) {
    281                             mRemoveListener.remove(mFirstDragPos);
    282                         }
    283                         unExpandViews(true);
    284                     } else {
    285                         if (mDropListener != null && mDragPos >= 0 && mDragPos < getCount()) {
    286                             mDropListener.drop(mFirstDragPos, mDragPos);
    287                         }
    288                         unExpandViews(false);
    289                     }
    290                     break;
    291 
    292                 case MotionEvent.ACTION_DOWN:
    293                 case MotionEvent.ACTION_MOVE:
    294                     int x = (int) ev.getX();
    295                     int y = (int) ev.getY();
    296                     dragView(x, y);
    297                     int itemnum = getItemForPosition(y);
    298                     if (itemnum >= 0) {
    299                         if (action == MotionEvent.ACTION_DOWN || itemnum != mDragPos) {
    300                             if (mDragListener != null) {
    301                                 mDragListener.drag(mDragPos, itemnum);
    302                             }
    303                             mDragPos = itemnum;
    304                             doExpansion();
    305                         }
    306                         int speed = 0;
    307                         adjustScrollBounds(y);
    308                         if (y > mLowerBound) {
    309                             // scroll the list up a bit
    310                             speed = y > (mHeight + mLowerBound) / 2 ? 16 : 4;
    311                         } else if (y < mUpperBound) {
    312                             // scroll the list down a bit
    313                             speed = y < mUpperBound / 2 ? -16 : -4;
    314                         }
    315                         if (speed != 0) {
    316                             int ref = pointToPosition(0, mHeight / 2);
    317                             if (ref == AdapterView.INVALID_POSITION) {
    318                                 //we hit a divider or an invisible view, check somewhere else
    319                                 ref = pointToPosition(0, mHeight / 2 + getDividerHeight() + 64);
    320                             }
    321                             View v = getChildAt(ref - getFirstVisiblePosition());
    322                             if (v!= null) {
    323                                 int pos = v.getTop();
    324                                 setSelectionFromTop(ref, pos - speed);
    325                             }
    326                         }
    327                     }
    328                     break;
    329             }
    330             return true;
    331         }
    332         return super.onTouchEvent(ev);
    333     }
    334 
    335     private void startDragging(Bitmap bm, int y) {
    336         stopDragging();
    337 
    338         mWindowParams = new WindowManager.LayoutParams();
    339         mWindowParams.gravity = Gravity.TOP;
    340         mWindowParams.x = 0;
    341         mWindowParams.y = y - mDragPoint + mCoordOffset;
    342 
    343         mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
    344         mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
    345         mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
    346                 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
    347                 | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
    348                 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
    349                 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
    350         mWindowParams.format = PixelFormat.TRANSLUCENT;
    351         mWindowParams.windowAnimations = 0;
    352 
    353         Context context = getContext();
    354         ImageView v = new ImageView(context);
    355         int backGroundColor = context.getResources().getColor(R.color.dragndrop_background);
    356         v.setBackgroundColor(backGroundColor);
    357         v.setImageBitmap(bm);
    358         mDragBitmap = bm;
    359 
    360         mWindowManager = (WindowManager)context.getSystemService("window");
    361         mWindowManager.addView(v, mWindowParams);
    362         mDragView = v;
    363     }
    364 
    365     private void dragView(int x, int y) {
    366         if (mRemoveMode == SLIDE) {
    367             float alpha = 1.0f;
    368             int width = mDragView.getWidth();
    369             if (x > width / 2) {
    370                 alpha = ((float)(width - x)) / (width / 2);
    371             }
    372             mWindowParams.alpha = alpha;
    373         }
    374         if (mRemoveMode == FLING) {
    375             mWindowParams.x = x;
    376         }
    377         mWindowParams.y = y - mDragPoint + mCoordOffset;
    378         mWindowManager.updateViewLayout(mDragView, mWindowParams);
    379     }
    380 
    381     private void stopDragging() {
    382         if (mDragView != null) {
    383             WindowManager wm = (WindowManager)getContext().getSystemService("window");
    384             wm.removeView(mDragView);
    385             mDragView.setImageDrawable(null);
    386             mDragView = null;
    387         }
    388         if (mDragBitmap != null) {
    389             mDragBitmap.recycle();
    390             mDragBitmap = null;
    391         }
    392     }
    393 
    394     public void setDragListener(DragListener l) {
    395         mDragListener = l;
    396     }
    397 
    398     public void setDropListener(DropListener l) {
    399         mDropListener = l;
    400     }
    401 
    402     public void setRemoveListener(RemoveListener l) {
    403         mRemoveListener = l;
    404     }
    405 
    406     public interface DragListener {
    407         void drag(int from, int to);
    408     }
    409     public interface DropListener {
    410         void drop(int from, int to);
    411     }
    412     public interface RemoveListener {
    413         void remove(int which);
    414     }
    415 }
    416