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.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 
     43     private ImageView mDragView;
     44     private WindowManager mWindowManager;
     45     private WindowManager.LayoutParams mWindowParams;
     46     /**
     47      * At which position is the item currently being dragged. Note that this
     48      * takes in to account header items.
     49      */
     50     private int mDragPos;
     51     /**
     52      * At which position was the item being dragged originally
     53      */
     54     private int mSrcDragPos;
     55     private int mDragPointX;    // at what x offset inside the item did the user grab it
     56     private int mDragPointY;    // at what y offset inside the item did the user grab it
     57     private int mXOffset;  // the difference between screen coordinates and coordinates in this view
     58     private int mYOffset;  // the difference between screen coordinates and coordinates in this view
     59     private DragListener mDragListener;
     60     private DropListener mDropListener;
     61     private RemoveListener mRemoveListener;
     62     private int mUpperBound;
     63     private int mLowerBound;
     64     private int mHeight;
     65     private GestureDetector mGestureDetector;
     66     private static final int FLING = 0;
     67     private static final int SLIDE = 1;
     68     private static final int TRASH = 2;
     69     private int mRemoveMode = -1;
     70     private Rect mTempRect = new Rect();
     71     private Bitmap mDragBitmap;
     72     private final int mTouchSlop;
     73     private int mItemHeightNormal;
     74     private int mItemHeightExpanded;
     75     private int mItemHeightHalf;
     76     private Drawable mTrashcan;
     77 
     78     public TouchInterceptor(Context context, AttributeSet attrs) {
     79         super(context, attrs);
     80         SharedPreferences pref = context.getSharedPreferences("Music", 3);
     81         mRemoveMode = pref.getInt("deletemode", -1);
     82         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
     83         Resources res = getResources();
     84         mItemHeightNormal = res.getDimensionPixelSize(R.dimen.normal_height);
     85         mItemHeightHalf = mItemHeightNormal / 2;
     86         mItemHeightExpanded = res.getDimensionPixelSize(R.dimen.expanded_height);
     87     }
     88 
     89     @Override
     90     public boolean onInterceptTouchEvent(MotionEvent ev) {
     91         if (mRemoveListener != null && mGestureDetector == null) {
     92             if (mRemoveMode == FLING) {
     93                 mGestureDetector = new GestureDetector(getContext(), new SimpleOnGestureListener() {
     94                     @Override
     95                     public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
     96                             float velocityY) {
     97                         if (mDragView != null) {
     98                             if (velocityX > 1000) {
     99                                 Rect r = mTempRect;
    100                                 mDragView.getDrawingRect(r);
    101                                 if ( e2.getX() > r.right * 2 / 3) {
    102                                     // fast fling right with release near the right edge of the 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      */
    156     private int myPointToPosition(int x, int y) {
    157 
    158         if (y < 0) {
    159             // when dragging off the top of the screen, calculate position
    160             // by going back from a visible item
    161             int pos = myPointToPosition(x, y + mItemHeightNormal);
    162             if (pos > 0) {
    163                 return pos - 1;
    164             }
    165         }
    166 
    167         Rect frame = mTempRect;
    168         final int count = getChildCount();
    169         for (int i = count - 1; i >= 0; i--) {
    170             final View child = getChildAt(i);
    171             child.getHitRect(frame);
    172             if (frame.contains(x, y)) {
    173                 return getFirstVisiblePosition() + i;
    174             }
    175         }
    176         return INVALID_POSITION;
    177     }
    178 
    179     private int getItemForPosition(int y) {
    180         int adjustedy = y - mDragPointY - mItemHeightHalf;
    181         int pos = myPointToPosition(0, adjustedy);
    182         if (pos >= 0) {
    183             if (pos <= mSrcDragPos) {
    184                 pos += 1;
    185             }
    186         } else if (adjustedy < 0) {
    187             // this shouldn't happen anymore now that myPointToPosition deals
    188             // with this situation
    189             pos = 0;
    190         }
    191         return pos;
    192     }
    193 
    194     private void adjustScrollBounds(int y) {
    195         if (y >= mHeight / 3) {
    196             mUpperBound = mHeight / 3;
    197         }
    198         if (y <= mHeight * 2 / 3) {
    199             mLowerBound = mHeight * 2 / 3;
    200         }
    201     }
    202 
    203     /*
    204      * Restore size and visibility for all listitems
    205      */
    206     private void unExpandViews(boolean deletion) {
    207         for (int i = 0;; i++) {
    208             View v = getChildAt(i);
    209             if (v == null) {
    210                 if (deletion) {
    211                     // HACK force update of mItemCount
    212                     int position = getFirstVisiblePosition();
    213                     int y = getChildAt(0).getTop();
    214                     setAdapter(getAdapter());
    215                     setSelectionFromTop(position, y);
    216                     // end hack
    217                 }
    218                 try {
    219                     layoutChildren(); // force children to be recreated where needed
    220                     v = getChildAt(i);
    221                 } catch (IllegalStateException ex) {
    222                     // layoutChildren throws this sometimes, presumably because we're
    223                     // in the process of being torn down but are still getting touch
    224                     // events
    225                 }
    226                 if (v == null) {
    227                     return;
    228                 }
    229             }
    230             ViewGroup.LayoutParams params = v.getLayoutParams();
    231             params.height = mItemHeightNormal;
    232             v.setLayoutParams(params);
    233             v.setVisibility(View.VISIBLE);
    234         }
    235     }
    236 
    237     /* Adjust visibility and size to make it appear as though
    238      * an item is being dragged around and other items are making
    239      * room for it:
    240      * If dropping the item would result in it still being in the
    241      * same place, then make the dragged listitem's size normal,
    242      * but make the item invisible.
    243      * Otherwise, if the dragged listitem is still on screen, make
    244      * it as small as possible and expand the item below the insert
    245      * point.
    246      * If the dragged item is not on screen, only expand the item
    247      * below the current insertpoint.
    248      */
    249     private void doExpansion() {
    250         int childnum = mDragPos - getFirstVisiblePosition();
    251         if (mDragPos > mSrcDragPos) {
    252             childnum++;
    253         }
    254         int numheaders = getHeaderViewsCount();
    255 
    256         View first = getChildAt(mSrcDragPos - getFirstVisiblePosition());
    257         for (int i = 0;; i++) {
    258             View vv = getChildAt(i);
    259             if (vv == null) {
    260                 break;
    261             }
    262 
    263             int height = mItemHeightNormal;
    264             int visibility = View.VISIBLE;
    265             if (mDragPos < numheaders && i == numheaders) {
    266                 // dragging on top of the header item, so adjust the item below
    267                 // instead
    268                 if (vv.equals(first)) {
    269                     visibility = View.INVISIBLE;
    270                 } else {
    271                     height = mItemHeightExpanded;
    272                 }
    273             } else if (vv.equals(first)) {
    274                 // processing the item that is being dragged
    275                 if (mDragPos == mSrcDragPos || getPositionForView(vv) == getCount() - 1) {
    276                     // hovering over the original location
    277                     visibility = View.INVISIBLE;
    278                 } else {
    279                     // not hovering over it
    280                     // Ideally the item would be completely gone, but neither
    281                     // setting its size to 0 nor settings visibility to GONE
    282                     // has the desired effect.
    283                     height = 1;
    284                 }
    285             } else if (i == childnum) {
    286                 if (mDragPos >= numheaders && mDragPos < getCount() - 1) {
    287                     height = mItemHeightExpanded;
    288                 }
    289             }
    290             ViewGroup.LayoutParams params = vv.getLayoutParams();
    291             params.height = height;
    292             vv.setLayoutParams(params);
    293             vv.setVisibility(visibility);
    294         }
    295     }
    296 
    297     @Override
    298     public boolean onTouchEvent(MotionEvent ev) {
    299         if (mGestureDetector != null) {
    300             mGestureDetector.onTouchEvent(ev);
    301         }
    302         if ((mDragListener != null || mDropListener != null) && mDragView != null) {
    303             int action = ev.getAction();
    304             switch (action) {
    305                 case MotionEvent.ACTION_UP:
    306                 case MotionEvent.ACTION_CANCEL:
    307                     Rect r = mTempRect;
    308                     mDragView.getDrawingRect(r);
    309                     stopDragging();
    310                     if (mRemoveMode == SLIDE && ev.getX() > r.right * 3 / 4) {
    311                         if (mRemoveListener != null) {
    312                             mRemoveListener.remove(mSrcDragPos);
    313                         }
    314                         unExpandViews(true);
    315                     } else {
    316                         if (mDropListener != null && mDragPos >= 0 && mDragPos < getCount()) {
    317                             mDropListener.drop(mSrcDragPos, mDragPos);
    318                         }
    319                         unExpandViews(false);
    320                     }
    321                     break;
    322 
    323                 case MotionEvent.ACTION_DOWN:
    324                 case MotionEvent.ACTION_MOVE:
    325                     int x = (int) ev.getX();
    326                     int y = (int) ev.getY();
    327                     dragView(x, y);
    328                     int itemnum = getItemForPosition(y);
    329                     if (itemnum >= 0) {
    330                         if (action == MotionEvent.ACTION_DOWN || itemnum != mDragPos) {
    331                             if (mDragListener != null) {
    332                                 mDragListener.drag(mDragPos, itemnum);
    333                             }
    334                             mDragPos = itemnum;
    335                             doExpansion();
    336                         }
    337                         int speed = 0;
    338                         adjustScrollBounds(y);
    339                         if (y > mLowerBound) {
    340                             // scroll the list up a bit
    341                             if (getLastVisiblePosition() < getCount() - 1) {
    342                                 speed = y > (mHeight + mLowerBound) / 2 ? 16 : 4;
    343                             } else {
    344                                 speed = 1;
    345                             }
    346                         } else if (y < mUpperBound) {
    347                             // scroll the list down a bit
    348                             speed = y < mUpperBound / 2 ? -16 : -4;
    349                             if (getFirstVisiblePosition() == 0
    350                                     && getChildAt(0).getTop() >= getPaddingTop()) {
    351                                 // if we're already at the top, don't try to scroll, because
    352                                 // it causes the framework to do some extra drawing that messes
    353                                 // up our animation
    354                                 speed = 0;
    355                             }
    356                         }
    357                         if (speed != 0) {
    358                             smoothScrollBy(speed, 30);
    359                         }
    360                     }
    361                     break;
    362             }
    363             return true;
    364         }
    365         return super.onTouchEvent(ev);
    366     }
    367 
    368     private void startDragging(Bitmap bm, int x, int y) {
    369         stopDragging();
    370 
    371         mWindowParams = new WindowManager.LayoutParams();
    372         mWindowParams.gravity = Gravity.TOP | Gravity.LEFT;
    373         mWindowParams.x = x - mDragPointX + mXOffset;
    374         mWindowParams.y = y - mDragPointY + mYOffset;
    375 
    376         mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
    377         mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
    378         mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
    379                 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
    380                 | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
    381                 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
    382                 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
    383         mWindowParams.format = PixelFormat.TRANSLUCENT;
    384         mWindowParams.windowAnimations = 0;
    385 
    386         Context context = getContext();
    387         ImageView v = new ImageView(context);
    388         //int backGroundColor = context.getResources().getColor(R.color.dragndrop_background);
    389         //v.setBackgroundColor(backGroundColor);
    390         v.setBackgroundResource(R.drawable.playlist_tile_drag);
    391         v.setPadding(0, 0, 0, 0);
    392         v.setImageBitmap(bm);
    393         mDragBitmap = bm;
    394 
    395         mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
    396         mWindowManager.addView(v, mWindowParams);
    397         mDragView = v;
    398     }
    399 
    400     private void dragView(int x, int y) {
    401         if (mRemoveMode == SLIDE) {
    402             float alpha = 1.0f;
    403             int width = mDragView.getWidth();
    404             if (x > width / 2) {
    405                 alpha = ((float)(width - x)) / (width / 2);
    406             }
    407             mWindowParams.alpha = alpha;
    408         }
    409 
    410         if (mRemoveMode == FLING || mRemoveMode == TRASH) {
    411             mWindowParams.x = x - mDragPointX + mXOffset;
    412         } else {
    413             mWindowParams.x = 0;
    414         }
    415         mWindowParams.y = y - mDragPointY + mYOffset;
    416         mWindowManager.updateViewLayout(mDragView, mWindowParams);
    417 
    418         if (mTrashcan != null) {
    419             int width = mDragView.getWidth();
    420             if (y > getHeight() * 3 / 4) {
    421                 mTrashcan.setLevel(2);
    422             } else if (width > 0 && x > width / 4) {
    423                 mTrashcan.setLevel(1);
    424             } else {
    425                 mTrashcan.setLevel(0);
    426             }
    427         }
    428     }
    429 
    430     private void stopDragging() {
    431         if (mDragView != null) {
    432             mDragView.setVisibility(GONE);
    433             WindowManager wm = (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 
    447     public void setTrashcan(Drawable trash) {
    448         mTrashcan = trash;
    449         mRemoveMode = TRASH;
    450     }
    451 
    452     public void setDragListener(DragListener l) {
    453         mDragListener = l;
    454     }
    455 
    456     public void setDropListener(DropListener l) {
    457         mDropListener = l;
    458     }
    459 
    460     public void setRemoveListener(RemoveListener l) {
    461         mRemoveListener = l;
    462     }
    463 
    464     public interface DragListener {
    465         void drag(int from, int to);
    466     }
    467     public interface DropListener {
    468         void drop(int from, int to);
    469     }
    470     public interface RemoveListener {
    471         void remove(int which);
    472     }
    473 }
    474