Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 自定義ListView實現拖拽ListItem項交換位置(附源碼)

自定義ListView實現拖拽ListItem項交換位置(附源碼)

編輯:關於Android編程

寫在前面的話
在上一篇實現了通過布局泵拿到不同布局為listitem布局,然後實現聯系人的ListView,這一章要做的是拖拽ListView的Item項,本章原理是在上一篇博客基礎之上的,上一篇博客:自定義Adapter並通過布局泵LayoutInflater抓取layout模板編輯每一個item

實現效果圖
 

說明
首先我們看到的上面這張圖就是實現的效果圖了。拖動之後數據項完成交換位置。

功能剖析
我們看到做的這個效果是一個拖拽ListView的Item項位置的功能,在布局方面還是用基於布局泵LayoutInflater來從不同的Layout模板拿到不同的布局然後將view返回。關於布局這一點的知識在上一篇有詳細說明,文章開頭已經說明,OK,下面我們來剖析一下這個拖動效果的實現吧,下面的文章將會以方法執行的順序來給出各個方法的代碼。然後依次剖析每個方法的作用。

方法執行順序
[DragView] -> [初始化ListViewContext,和觸發move事件的最小距離變量]
[onInterceptTouchEvent] -> [初始化拖動的變量]
[startDrag] -> [准備拖動的影像、window等變量]
[stopDrag] -> [判斷重置拖動的影像]
[startDrag] -> [准備拖動的影像、window等變量]
[onTouchEvent] -> [判斷點擊事件、根據動作做不同操作,或重新繪制Move影響,或者Stop停止拖動。交換數據,也就是下面的這兩個方法]
[onDrag] -> [實現滾動的動作]
[onDrop] -> [實現數據item位置切換]
注意
上面的方法執行順序只是大概邏輯,這其中還有判斷和方法中調用其他方法,所以方法的調用是多次的,大家湊合看一下方法的大體功能吧,需要注意的是我們重寫的onTouchEvent是要一直不斷的監聽我們的按鍵的,如果為Move的話也就是會一直不斷的去調用onDrag方法去實現滾動的動作,在此我做了測試,給大家看一下打印的日志如下:
 
我們看到move日志被執行了多次。說明我們在拖動的時候一直在執行這個方法。下面我會給大家自定義ListView的代碼,代碼中注釋量很大,可讀性很高,推薦大家參考上面我給出的方法運行順序去理解,最後我將給出運行源碼。
復制代碼 代碼如下:
package com.example.draglistview;
import com.example.draglistview.MainActivity.DragListAdapter;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.PixelFormat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Toast;
public class DragView extends ListView{
private ImageView imageView; //被拖動的圖片
private int scaledTouchSlop; //判斷拖動的距離

private int dragSrcPosition; //手指在touch事件觸摸時候的原始位置
private int dragPosition; //手指拖動列表項時候的位置

private int dragPoint; //手指點擊位置在當前數據項item中的位置,只有Y坐標
private int dragOffset; //當前視圖listview在屏幕中的位置,只有Y坐標

private int upScrollBounce; //向上滑動的邊界
private int downScrollBounce; //拖動的時候向下滑動的邊界

private WindowManager windowManager = null; //窗口管理類
//窗口參數類
private WindowManager.LayoutParams layoutParams = null;



//注意該View如果在Layout xml 注冊使用的話必須使用下面的這個構造進行初始化
public DragView(Context context, AttributeSet attrs) {
super(context, attrs);
//觸發移動事件的最小距離
scaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
//重寫於absListView
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {

if(ev.getAction() == MotionEvent.ACTION_DOWN){
//獲取的該touch事件的x坐標和y坐標,該坐標是相對於組件的左上角的位置
int x = (int) ev.getX();
int y = (int) ev.getY();
//賦值手指點擊時候的開始坐標
dragSrcPosition = dragPosition = this.pointToPosition(x, y);
//如果點擊在列表之外,也就是不允許的位置
if(dragPosition == AdapterView.INVALID_POSITION){
//直接執行父類,不做任何操作
return super.onInterceptTouchEvent(ev);
}

/***
* 鎖定手指touch的列表item,
* 參數為屏幕的touch坐標減去listview左上角的坐標
* 這裡的getChildAt方法參數為相對於組件左上角坐標為00的情況
* 故有下面的這種參數算法
*/
ViewGroup itemView = (ViewGroup) this.getChildAt(dragPosition-this.getFirstVisiblePosition());
/****
* 說明:getX Y為touch點相對於組件左上角的距離
* getRawX 、Y 為touch點相對於屏幕左上角的距離
* 參考http://blog.csdn.net/love_world_/article/details/8164293
*/
//touch點的view相對於該childitem的top坐標的距離
dragPoint = y-itemView.getTop();
//為距離屏幕左上角的Y減去距離組件左上角的Y,其實就是
//組件上方的view+標題欄+狀態欄的Y
dragOffset = (int) (ev.getRawY()-y);

//拿到拖動的imageview對象
View drager = itemView.findViewById(R.id.imageView1);

//判斷條件為拖動touch圖片是否為null和touch的位置,是否符合
if(drager != null && x>drager.getLeft()-20){

//判斷得出向上滑動和向下滑動的值
upScrollBounce = Math.min(y-scaledTouchSlop, getHeight()/3);
downScrollBounce = Math.max(y+scaledTouchSlop, getHeight()*2/3);
//啟用繪圖緩存
itemView.setDrawingCacheEnabled(true);
//根據圖像緩存拿到對應位圖
Bitmap bm = Bitmap.createBitmap(itemView.getDrawingCache());
startDrag(bm, y);
}
return false;
}
return super.onInterceptTouchEvent(ev);
}


//重寫OnTouchEvent,觸摸事件
@Override
public boolean onTouchEvent(MotionEvent ev) {
if(imageView != null && dragPosition != INVALID_POSITION){
int currentAction = ev.getAction();

switch (currentAction) {
case MotionEvent.ACTION_UP:
int upY = (int) ev.getY();
//還有一些操作
stopDrag();
onDrop(upY);
break;
case MotionEvent.ACTION_MOVE:
Log.v("move", "move---------");
int moveY = (int) ev.getY();
onDrag(moveY);
break;
default:
break;
}
return true;
}
//決定了選中的效果
return super.onTouchEvent(ev);
}



/****
* 准備拖動,初始化拖動時的影像,和一些window參數
* @param bm 拖動緩存位圖
* @param y 拖動之前touch的位置
*/
public void startDrag(Bitmap bm,int y){
stopDrag();
layoutParams = new WindowManager.LayoutParams();
//設置重力
layoutParams.gravity = Gravity.TOP;
//橫軸坐標不變
layoutParams.x = 0;
/**
*
* y軸坐標為 視圖相對於自身左上角的Y-touch點在列表項中的y
* +視圖相對於屏幕左上角的Y,=
* 該view相對於屏幕左上角的位置
*/
layoutParams.y = y-dragPoint+dragOffset;
/****
* 寬度和高度都為wrapContent
*/
layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;

/****
* 設置該layout參數的一些flags參數
*/
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
//設置該window項是半透明的格式
layoutParams.format = PixelFormat.TRANSLUCENT;
//設置沒有動畫
layoutParams.windowAnimations = 0;

//配置一個影像ImageView
ImageView imageViewForDragAni = new ImageView(getContext());
imageViewForDragAni.setImageBitmap(bm);
//配置該windowManager
windowManager = (WindowManager) this.getContext().getSystemService("window");
windowManager.addView(imageViewForDragAni, layoutParams);
imageView = imageViewForDragAni;
}

/***
* 停止拖動,去掉拖動時候的影像
*/
public void stopDrag(){
if(imageView != null){
windowManager.removeView(imageView);
imageView = null;
}
}


/****
* 拖動方法
* @param y
*/
public void onDrag(int y){

if(imageView != null){
//透明度
layoutParams.alpha = 0.8f;
layoutParams.y = y-this.dragPoint+this.dragOffset;
windowManager.updateViewLayout(imageView, layoutParams);
}


//避免拖動到分割線返回-1
int tempPosition = this.pointToPosition(0, y);
if(tempPosition != this.INVALID_POSITION){
this.dragPosition = tempPosition;
}


int scrollHeight = 0;
if(y<upScrollBounce){
scrollHeight = 8;//定義向上滾動8個像素,如果可以向上滾動的話
}else if(y>downScrollBounce){
scrollHeight = -8;//定義向下滾動8個像素,,如果可以向上滾動的話
}

if(scrollHeight!=0){
//真正滾動的方法setSelectionFromTop()
setSelectionFromTop(dragPosition, getChildAt(dragPosition-getFirstVisiblePosition()).getTop()+scrollHeight);
}
}


/***
* 拖動放下的時候
* param : y
*/
public void onDrop(int y){
int tempPosition = this.pointToPosition(0, y);
if(tempPosition != this.INVALID_POSITION){
this.dragPosition = tempPosition;
}

//超出邊界處理
if(y<getChildAt(1).getTop()){
//超出上邊界
dragPosition = 1;
}else if(y>getChildAt(getChildCount()-1).getBottom()){
//超出下邊界
dragPosition = getAdapter().getCount()-1;
//
}
//數據交換
if(dragPosition>0&&dragPosition<getAdapter().getCount()){
@SuppressWarnings("unchecked")
DragListAdapter adapter = (DragListAdapter)getAdapter();
//原始位置的item
String dragItem = adapter.getItem(dragSrcPosition);
adapter.remove(dragItem);
adapter.insert(dragItem, dragPosition);
Toast.makeText(getContext(), adapter.getList().toString(), Toast.LENGTH_SHORT).show();
}
}
}

寫在後面的話
以上就為自定義ListView的源碼了。當然還有部分未給出的代碼。包括MainActivity、3個Layout xml 文件。[比較簡單] 如果你看懂了[或者有一些疑問]上面的這部分代碼,你可以下載我的源碼查相關的API和案例去學習,去進步,祝成功!

源碼下載
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved