对于ListView的拖动效果,其实也没什么神秘的,在android packages/apps/Music中例子中就有这样的效果实现,这是android系统自带的。如果大家没有这部分源码,最后的实例我会一起打包放在rar文件中,有兴趣的可以自己读一下。 说起这个拖动效果,其实以前也做过,不过不是用android自己的实现方法,而是自己使用逻辑实现的。看了Music那部分代码之后,发现其实也差不多,使用的逻辑算法是差不太多的。 Music下面TouchInterceptor.java这个java文件就是自定义的listview的实现。 拖动的效果整体来说是这样的: 首先通过重写onInterceptTouchEvent方法获取在点击了item时,记录点击的point,同时将点击的item所覆盖的Rect记录下,并且将此时的item映射成一张大小一样(当然也可以改变大小,这个随程序员)的Bitmap,当开始拖动之后创建一个ImageView在Window上,将Bitmap作为src传给ImageView. 可能有童鞋不知道onInterceptTouchEvent这个方法的意思,它其实相当于onTouchEvent,不过onTouchEvent事件的触发是基于onInterceptTouchEvent没有处理事件的时候。就是说只有onInterceptTouchEvent没有处理你想处理的事件那么 onTouchEvent才会获得处理你想处理的事件的权利。 代码如下: [mw_shl_code=java,true]public class MyDragListView extends ListView { private ImageView mDragView; private WindowManager mWindowManager; private WindowManager.LayoutParams mWindowParams; private int mDragPos; // which item is being dragged private int mFirstDragPos; // where was the dragged item originally private int mDragPoint; // at what offset inside the item did the user grab // it private int mCoordOffset; // the difference between screen coordinates and // coordinates in this view private DragListener mDragListener; private DropListener mDropListener; private RemoveListener mRemoveListener; private int mUpperBound; private int mLowerBound; private int mHeight; private GestureDetector mGestureDetector; public static final int FLING = 0; public static final int SLIDE_RIGHT = 1; public static final int SLIDE_LEFT = 2; private int mRemoveMode = -1; private Rect mTempRect = new Rect(); private Bitmap mDragBitmap; private final int mTouchSlop; private int mItemHeightNormal = -1; private int mItemHeightExpanded = -1; public MyDragListView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyDragListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); Resources res = getResources(); mItemHeightNormal = res.getDimensionPixelSize(R.dimen.normal_height); mItemHeightExpanded = res .getDimensionPixelSize(R.dimen.expanded_height); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (mRemoveListener != null && mGestureDetector == null) { if (mRemoveMode == FLING) { mGestureDetector = new GestureDetector(getContext(), new SimpleOnGestureListener() { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { if (mDragView != null) { if (velocityX > 1000) { Rect r = mTempRect; mDragView.getDrawingRect(r); if (e2.getX() > r.right * 2 / 3) { // fast fling right with release // near the right edge of the screen stopDragging(); mRemoveListener .remove(mFirstDragPos); unExpandViews(true); } } // flinging while dragging should have no // effect return true; } return false; } }); } } if (mDragListener != null || mDropListener != null) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: int x = (int) ev.getX(); int y = (int) ev.getY(); int itemnum = pointToPosition(x, y); if (itemnum == AdapterView.INVALID_POSITION) { break; } ViewGroup item = (ViewGroup) getChildAt(itemnum - getFirstVisiblePosition()); mDragPoint = y - item.getTop(); mCoordOffset = ((int) ev.getRawY()) - y; if (x < 200) { item.setDrawingCacheEnabled(true); // Create a copy of the drawing cache so that it does not // get recycled // by the framework when the list tries to clean up memory Bitmap bitmap = Bitmap.createBitmap(item.getDrawingCache()); startDragging(bitmap, y); mDragPos = itemnum; mFirstDragPos = mDragPos; mHeight = getHeight(); int touchSlop = mTouchSlop; mUpperBound = Math.min(y - touchSlop, mHeight / 3); mLowerBound = Math.max(y + touchSlop, mHeight * 2 / 3); return false; } mDragView = null; break; } } return super.onInterceptTouchEvent(ev); } /* * pointToPosition() doesn't consider invisible views, but we need to, so * implement a slightly different version. */ private int myPointToPosition(int x, int y) { if (y < 0) { // when dragging off the top of the screen, calculate position // by going back from a visible item int pos = myPointToPosition(x, y + mItemHeightNormal); if (pos > 0) { return pos - 1; } } Rect frame = mTempRect; final int count = getChildCount(); for (int i = count - 1; i >= 0; i--) { final View child = getChildAt(i); child.getHitRect(frame); if (frame.contains(x, y)) { return getFirstVisiblePosition() + i; } } return INVALID_POSITION; } private int getItemForPosition(int y) { int adjustedy = y - mDragPoint - 40; int pos = myPointToPosition(0, adjustedy); if (pos >= 0) { if (pos <= mFirstDragPos) { pos += 1; } } else if (adjustedy < 0) { pos = 0; } return pos; } private void adjustScrollBounds(int y) { if (y >= mHeight / 3) { mUpperBound = mHeight / 3; } if (y <= mHeight * 2 / 3) { mLowerBound = mHeight * 2 / 3; } } /* * Restore size and visibility for all listitems */ private void unExpandViews(boolean deletion) { for (int i = 0;; i++) { View v = getChildAt(i); if (v == null) { if (deletion) { // HACK force update of mItemCount int position = getFirstVisiblePosition(); int y = getChildAt(0).getTop(); setAdapter(getAdapter()); setSelectionFromTop(position, y); // end hack } layoutChildren(); // force children to be recreated where needed v = getChildAt(i); if (v == null) { break; } } ViewGroup.LayoutParams params = v.getLayoutParams(); params.height = mItemHeightNormal; v.setLayoutParams(params); v.setVisibility(View.VISIBLE); } } /* * Adjust visibility and size to make it appear as though an item is being * dragged around and other items are making room for it: If dropping the * item would result in it still being in the same place, then make the * dragged listitem's size normal, but make the item invisible. Otherwise, * if the dragged listitem is still on screen, make it as small as possible * and expand the item below the insert point. If the dragged item is not on * screen, only expand the item below the current insertpoint. */ private void doExpansion() { int childnum = mDragPos - getFirstVisiblePosition(); if (mDragPos > mFirstDragPos) { childnum++; } View first = getChildAt(mFirstDragPos - getFirstVisiblePosition()); for (int i = 0;; i++) { View vv = getChildAt(i); if (vv == null) { break; } int height = mItemHeightNormal; int visibility = View.VISIBLE; if (vv.equals(first)) { // processing the item that is being dragged if (mDragPos == mFirstDragPos) { // hovering over the original location visibility = View.INVISIBLE; } else { // not hovering over it height = 1; } } else if (i == childnum) { if (mDragPos < getCount() - 1) { height = mItemHeightExpanded; } } ViewGroup.LayoutParams params = vv.getLayoutParams(); params.height = height; vv.setLayoutParams(params); vv.setVisibility(visibility); } } @Override public boolean onTouchEvent(MotionEvent ev) { if (mGestureDetector != null) { mGestureDetector.onTouchEvent(ev); } if ((mDragListener != null || mDropListener != null) && mDragView != null) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: Rect r = mTempRect; mDragView.getDrawingRect(r); stopDragging(); if (mRemoveMode == SLIDE_RIGHT && ev.getX() > r.left + (r.width() * 3 / 4)) { if (mRemoveListener != null) { mRemoveListener.remove(mFirstDragPos); } unExpandViews(true); } else if (mRemoveMode == SLIDE_LEFT && ev.getX() < r.left + (r.width() / 4)) { if (mRemoveListener != null) { mRemoveListener.remove(mFirstDragPos); } unExpandViews(true); } else { if (mDropListener != null && mDragPos >= 0 && mDragPos < getCount()) { mDropListener.drop(mFirstDragPos, mDragPos); } unExpandViews(false); } break; case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: int x = (int) ev.getX(); int y = (int) ev.getY(); dragView(x, y); int itemnum = getItemForPosition(y); if (itemnum >= 0) { if (action == MotionEvent.ACTION_DOWN || itemnum != mDragPos) { if (mDragListener != null) { mDragListener.drag(mDragPos, itemnum); } mDragPos = itemnum; doExpansion(); } int speed = 0; adjustScrollBounds(y); if (y > mLowerBound) { // scroll the list up a bit speed = y > (mHeight + mLowerBound) / 2 ? 16 : 4; } else if (y < mUpperBound) { // scroll the list down a bit speed = y < mUpperBound / 2 ? -16 : -4; } if (speed != 0) { int ref = pointToPosition(0, mHeight / 2); if (ref == AdapterView.INVALID_POSITION) { // we hit a divider or an invisible view, check // somewhere else ref = pointToPosition(0, mHeight / 2 + getDividerHeight() + 64); } View v = getChildAt(ref - getFirstVisiblePosition()); if (v != null) { int pos = v.getTop(); setSelectionFromTop(ref, pos - speed); } } } break; } return true; } return super.onTouchEvent(ev); } private void startDragging(Bitmap bm, int y) { stopDragging(); mWindowParams = new WindowManager.LayoutParams(); mWindowParams.gravity = Gravity.TOP; mWindowParams.x = 0; mWindowParams.y = y - mDragPoint + mCoordOffset; mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT; mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT; mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; mWindowParams.format = PixelFormat.TRANSLUCENT; mWindowParams.windowAnimations = 0; ImageView v = new ImageView(getContext()); // int backGroundColor = // getContext().getResources().getColor(R.color.dragndrop_background); // v.setBackgroundColor(dragndropBackgroundColor); v.setBackgroundColor(Color.BLUE); v.setImageBitmap(bm); mDragBitmap = bm; mWindowManager = (WindowManager) getContext() .getSystemService("window"); mWindowManager.addView(v, mWindowParams); mDragView = v; } private void dragView(int x, int y) { float alpha = 1.0f; int width = mDragView.getWidth(); if (mRemoveMode == SLIDE_RIGHT) { if (x > width / 2) { alpha = ((float) (width - x)) / (width / 2); } mWindowParams.alpha = alpha; } else if (mRemoveMode == SLIDE_LEFT) { if (x < width / 2) { alpha = ((float) x) / (width / 2); } mWindowParams.alpha = alpha; } mWindowParams.y = y - mDragPoint + mCoordOffset; mWindowManager.updateViewLayout(mDragView, mWindowParams); } private void stopDragging() { if (mDragView != null) { mDragView.setVisibility(GONE); WindowManager wm = (WindowManager) getContext().getSystemService( "window"); wm.removeView(mDragView); mDragView.setImageDrawable(null); mDragView = null; } if (mDragBitmap != null) { mDragBitmap.recycle(); mDragBitmap = null; } } public void setDragListener(DragListener l) { mDragListener = l; } public void setDropListener(DropListener l) { mDropListener = l; } public void setRemoveListener(RemoveListener l) { mRemoveListener = l; } public interface DragListener { void drag(int from, int to); } public interface DropListener { void drop(int from, int to); } public interface RemoveListener { void remove(int which); } }[/mw_shl_code] 在效果的实现中,几个需要注意的地方,我给大家指一下也方便大家更改它的代码: 1)mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();此方法返回一个常量,这个常量是根据手机屏幕参数返回的,简单的意思就是:当手滑动该控件view距离超过这个常量的大小才能算作是用户的滑动,小于此值,被认为是意外(抖动等)。 2)ViewGroup item = (ViewGroup) getChildAt(itemnum - getFirstVisiblePosition());这句话就是说,获得点击的item所代表的ViewGroup,这里值得注意的是:如果你所定义的item中是用android.R.layout.~下面的自带布局的话,这句话是有可能会报错的,为何呢?因为在android.R.layout下面的有些布局直接继承自View而不是继承自ViewGroup,而ViewGroup是View的子类,很显然android.R.layout.~ 不能强转成ViewGroup,这点一定要注意。(举个例子:如果你用android自带的android.R.layout.android.R.layout.simple_list_item_1, 而这个布局文件中只是一个textview它直接继承自view,当它调用(ViewGroup) getChildAt(itemnum - getFirstVisiblePosition());这句话强制转型的话必然会报错)。(关于View和ViewGroup不在讨论中,有兴趣的话可以去google) 3)item.setDrawingCacheEnabled(true);Bitmap bitmap = Bitmap.createBitmap(item.getDrawingCache()); 这两句话就是将刚点击的item转成一张Bitmap。前一句是说是否为item设置画面的缓存,而只有存在这样一个item画面的缓存我们才能根据它来create一个Bitmap。 4)在item.setDrawingCacheEnabled(true);前有一个if判断这里面判断的条件就是用户点击的位置,哪个位置触发拖动效果,在这里我设置的是为x<200表示当点的x坐标小于这200可以触发拖动。 5)ImageView v = new ImageView(getContext());v.setBackgroundColor(Color.BLUE);v.setImageBitmap(bm); 这3句代码就是我们先前所说的映射一样的东西,相当于我们拖动时的控件画面,而我设置他拖动的背景为蓝色。 对于其他一些相关代码,如果有想研究又有些看不懂的又想知道什么意思的可以留言,有时间我会来解释一下的。 最后说下这个拖动效果的使用方法: (1)首先在布局文件中定义这个ListView: [mw_shl_code=java,true]<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > < com.godxj.android.listviewdrag.MyDragListView android:id="@android:id/list" android:layout_width="fill_parent" android:layout_height="fill_parent" /> < /LinearLayout>[/mw_shl_code] (2)然后在MainActivity中像操作listview一样对其设置adapter: [mw_shl_code=java,true]MyDragListView lv = (MyDragListView) getListView(); adapter = new MyListViewAdapter(this); lv.setAdapter(adapter);[/mw_shl_code] 这里的adapter ,很简单就是一个LinearLayout中包含一个TextView,由于这里的LinearLayout是ViewGroup的子类所以是可以强转成ViewGroup的。 (3)最后,也就是重点: 为MyDragListView添加监听器:也就是自定义ListView提供的三个Listener的实现: [mw_shl_code=java,true]public interface DragListener { void drag(int from, int to); } public interface DropListener { void drop(int from, int to); } public interface RemoveListener { void remove(int which); }[/mw_shl_code] 这里面:drag这个方法是在当你拖动item并且拖动到某一个item上面的时候触发,drop当你停止拖动时触发,remove当删除一个item时触发。 [mw_shl_code=java,true]lv.setDragListener(new MyDragListView.DragListener() { //拖动到某个item的时候 @Override public void drag(int from, int to) { System.out.println("drag"); System.out.println("from = " + from + ",to = " + to); } }); lv.setDropListener(new MyDragListView.DropListener() { //停止拖动的时候触发 @Override public void drop(int from, int to) { String fromStr = array[from]; array[from] = array[to]; array[to] = fromStr; adapter.notifyDataSetChanged(); } }); lv.setRemoveListener(new MyDragListView.RemoveListener() { //删除的时候触发 @Override public void remove(int which) { System.out.println("remove"); System.out.println("which= " + which); } });[/mw_shl_code] 这里我只实现了互换的功能,其他其实都是类似的,大家可以自己想到,在实现互换的时候说起来是笨办法,不过处理起来并不见得不好。我将数据进行了互换,然后notifyDataSetChanged();如果想删除或者其他操作一样可以直接真对数据进行操作,只要记得调用notifyDataSetChanged(); 最后:看下简单的效果图:
实例代码: ListViewDrag.rar (4.21 MB, 下载次数: 117)
Music源码比较大代码中Music/src/com/android/music/TouchInterceptor.java改文件就是自定义ListView |
|