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