ListView 拖拽Item交換位置
最近一個專案中要求用到交換任意兩個ListView裡面Item位置,需要實現這個功能。因此,本屌也是通過學習相關例子,重繪封裝了一番,並且實現了這個功能。下面,就一起來看看是怎麼樣實現的吧。
1、XML佈局
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white" android:orientation="vertical" > <com.czl.struct.widget.DragListView android:id="@+id/dragLvi" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#FFFFFF" android:cacheColorHint="@android:color/transparent" android:divider="#DCD7CF" android:dividerHeight="1dp" android:fadingEdgeLength="0dip" android:focusable="false" android:focusableInTouchMode="false" android:listSelector="@android:color/transparent" android:scrollbars="vertical" android:typeface="sans" > </com.czl.struct.widget.DragListView> </LinearLayout>
是不是和平時引入ListView沒有任何區別呢,只不過把LIstView換成了我們自定義的DragListView。
2、自定義DragListView
現在我們需要實現我們自定義的DragListView,並且實現相關交換位置的介面即可。先看一下需要實現的介面,程式碼如下所示:
public interface DragItemChangeListener {
public void onDragItemChange(int dragSrcPosition,int dragPosition);
}
是不是很簡單啊,我們只需要為DragListView設定這個拖拽事件監聽即可。該事件回撥方法看名字也很容易理解,就是當位置被交換時,做相應回撥處理即可,其中第一個引數dragSrcPosition是交換前的位置,dragPosition是交換後的位置。接下來貼出DragListView的程式碼:
public class DragListView extends ListView { private WindowManager windowManager; // windows視窗控制類 private WindowManager.LayoutParams windowParams; // 用於控制拖拽項的顯示的引數 private ImageView dragImageView; // 被拖拽的項(item),其實就是一個ImageView private int dragSrcPosition; // 手指拖動項原始在列表中的位置 private int dragPosition; // 手指點選準備拖動的時候,當前拖動項在列表中的位置. private int dragPoint; // 在當前資料項中的位置 private int dragOffset; // 當前檢視和螢幕的距離(這裡只使用了y方向上) private int upScrollBounce; // 拖動的時候,開始向上滾動的邊界 private int downScrollBounce; // 拖動的時候,開始向下滾動的邊界 private final static int step = 1; // ListView 滑動步伐. private int current_Step; // 當前步伐. private int dragImageSourceId; private DragItemChangeListener dragItemChangeListener; /*** * 構造方法 * * @param context * @param attrs */ public DragListView(Context context, AttributeSet attrs) { super(context, attrs); } public void setDragImageSourceId(int dragImageSourceId) { this.dragImageSourceId = dragImageSourceId; } public void setDragItemChangeListener(DragItemChangeListener dragItemChangeListener) { this.dragItemChangeListener = dragItemChangeListener; } /*** * touch事件攔截 */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { // 按下 if (ev.getAction() == MotionEvent.ACTION_DOWN) { int x = (int) ev.getX();// 獲取相對與ListView的x座標 int y = (int) ev.getY();// 獲取相應與ListView的y座標 dragSrcPosition = dragPosition = pointToPosition(x, y); // 無效不進行處理 if (dragPosition == AdapterView.INVALID_POSITION) { return super.onInterceptTouchEvent(ev); } // 獲取當前位置的檢視(可見狀態) ViewGroup itemView = (ViewGroup) getChildAt(dragPosition - getFirstVisiblePosition()); // 獲取到的dragPoint其實就是在你點選指定item項中的高度. dragPoint = y - itemView.getTop(); // 這個值是固定的:其實就是ListView這個控制元件與螢幕最頂部的距離(一般為標題欄+狀態列). dragOffset = (int) (ev.getRawY() - y); // 獲取可拖拽的圖示 View dragger = itemView.findViewById(dragImageSourceId); // x > dragger.getLeft() - 20這句話為了更好的觸控(-20可以省略) if (dragger != null && x > dragger.getLeft() - 20) { upScrollBounce = getHeight() / 3;// 取得向上滾動的邊際,大概為該控制元件的1/3 downScrollBounce = getHeight() * 2 / 3;// 取得向下滾動的邊際,大概為該控制元件的2/3 itemView.setDrawingCacheEnabled(true);// 開啟cache. Bitmap bm = Bitmap.createBitmap(itemView.getDrawingCache());// 根據cache建立一個新的bitmap物件. startDrag(bm, y);// 初始化影像 } } return super.onInterceptTouchEvent(ev); } /** * 觸控事件處理 */ @Override public boolean onTouchEvent(MotionEvent ev) { // item的view不為空,且獲取的dragPosition有效 if (dragImageView != null && dragPosition != INVALID_POSITION) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_UP: int upY = (int) ev.getY(); stopDrag(); onDrop(upY); break; case MotionEvent.ACTION_MOVE: int moveY = (int) ev.getY(); onDrag(moveY); break; case MotionEvent.ACTION_DOWN: break; default: break; } return true;// 取消ListView滑動. } return super.onTouchEvent(ev); } /** * 準備拖動,初始化拖動項的影象 * * @param bm * @param y */ private void startDrag(Bitmap bm, int y) { // stopDrag(); /*** * 初始化window. */ windowParams = new WindowManager.LayoutParams(); windowParams.gravity = Gravity.TOP; windowParams.x = 0; windowParams.y = y - dragPoint + dragOffset; windowParams.width = WindowManager.LayoutParams.WRAP_CONTENT; windowParams.height = WindowManager.LayoutParams.WRAP_CONTENT; windowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE// 不需獲取焦點 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE// 不需接受觸控事件 | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON// 保持裝置常開,並保持亮度不變。 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;// 窗口占滿整個螢幕,忽略周圍的裝飾邊框(例如狀態列)。此視窗需考慮到裝飾邊框的內容。 windowParams.format = PixelFormat.TRANSLUCENT;// 預設為不透明,這裡設成透明效果. windowParams.windowAnimations = 0;// 視窗所使用的動畫設定 ImageView imageView = new ImageView(getContext()); imageView.setImageBitmap(bm); windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); windowManager.addView(imageView, windowParams); dragImageView = imageView; } /** * 拖動執行,在Move方法中執行 * * @param y */ public void onDrag(int y) { int drag_top = y - dragPoint;// 拖拽view的top值不能<0,否則則出界. if (dragImageView != null && drag_top >= 0) { windowParams.alpha = 0.5f;// 透明度 windowParams.y = y - dragPoint + dragOffset;// 移動y值.//記得要加上dragOffset,windowManager計算的是整個螢幕.(標題欄和狀態列都要算上) windowManager.updateViewLayout(dragImageView, windowParams);// 時時移動. } // 為了避免滑動到分割線的時候,返回-1的問題 int tempPosition = pointToPosition(0, y); if (tempPosition != INVALID_POSITION) { dragPosition = tempPosition; } doScroller(y); } /*** * ListView的移動. * 要明白移動原理:當映像移動到下端的時候,ListView向上滑動,當映像移動到上端的時候,ListView要向下滑動。正好和實際的相反. * */ public void doScroller(int y) { // ListView需要下滑 if (y < upScrollBounce) { current_Step = step + (upScrollBounce - y) / 10;// 時時步伐 }// ListView需要上滑 else if (y > downScrollBounce) { current_Step = -(step + (y - downScrollBounce)) / 10;// 時時步伐 } else { current_Step = 0; } // 獲取你拖拽滑動到位置及顯示item相應的view上(注:可顯示部分)(position) View view = getChildAt(dragPosition - getFirstVisiblePosition()); // 真正滾動的方法setSelectionFromTop() setSelectionFromTop(dragPosition, view.getTop() + current_Step); } /** * 停止拖動,刪除影像 */ public void stopDrag() { if (dragImageView != null) { windowManager.removeView(dragImageView); dragImageView = null; } } /** * 拖動放下的時候 * * @param y */ public void onDrop(int y) { // 為了避免滑動到分割線的時候,返回-1的問題 int tempPosition = pointToPosition(0, y); if (tempPosition != INVALID_POSITION) { dragPosition = tempPosition; } // 超出邊界處理(如果向上超過第二項Top的話,那麼就放置在第一個位置) if (y < getChildAt(0).getTop()) { // 超出上邊界 dragPosition = 0; // 如果拖動超過最後一項的最下邊那麼就防止在最下邊 } else if (y > getChildAt(getChildCount() - 1).getBottom()) { // 超出下邊界 dragPosition = getAdapter().getCount() - 1; } // 資料交換 if (dragPosition < getAdapter().getCount()) { dragItemChangeListener.onDragItemChange(dragSrcPosition, dragPosition); } } }
其中setDragItemChangeListener(DragItemChangeListener dragItemChangeListener) 就是我們要注入介面實現的地方。setDragImageSourceId(int dragImageSourceId) 方法可以設定您想拖拽的圖示資源。接下來萬事俱備,只欠東風了。
3、引入DragListView
<pre name="code" class="java">廢話不說,直接上程式碼:
<pre name="code" class="java">public class DragListActivity extends BaseActivity implements DragItemChangeListener {
private DragListView dragListView;
private ArrayList<HashMap<String, Object>> list;
private DemoAdapter demoAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_drag_list);
init();
findViewById();
}
@Override
public void init() {
list = new ArrayList<HashMap<String, Object>>();
for (int i = 0; i < 20; i++) {
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("numbTV", "D138026594664912200" + i);
map.put("adrTv", "浙江省杭州市西湖區小和山");
map.put("timeTv", "2013-09-27 15:23");
map.put("callTv", "18778900578");
list.add(map);
}
}
@Override
public void findViewById() {
dragListView = (DragListView) findViewById(R.id.dragLvi);
demoAdapter = new DemoAdapter(DragListActivity.this, list);
dragListView.setAdapter(demoAdapter);
dragListView.setDragImageSourceId(R.id.imageView1);
dragListView.setDragItemChangeListener(this);
}
@Override
public void Message(android.os.Message msg) {
}
@Override
public void onDragItemChange(int dragSrcPosition, int dragPosition) {
HashMap<String, Object> map = list.get(dragSrcPosition);
list.remove(dragSrcPosition);
list.add(dragPosition, map);
demoAdapter.notifyDataSetChanged();
}
}
介面卡就和平時使用的BaseAdapter沒有任何區別,這裡就不再贅述了,首先我們初始化了20條資料,然後我們重點關注setDragImageSourceId()和setItemChangeListener()這兩行程式碼,聰明的你肯定已經完全明白了吧,這裡就是我們設定自定義拖拽圖示以及新增交換位置監聽的實現。onDragItemChange()就是我們回撥後具體交換位置重新整理UI介面的程式碼啦。
4、下載地址
5、總結
總的來說,要實現ListView交換Item項位置,只需三步,第一,XML佈局檔案裡引入我們自定義的DragListView,第二,為dragListView設定拖拽圖片資源和事件觸發監聽,第三,實現onDragItemChange交換位置介面,進行位置交換並且重新整理UI介面。很簡單,有木有?大家都來試一下吧。