趁着周一休息,更新一下博客。最近项目中使用到了分组管理,需要实现Listview的Item拖动处理。查略一下资料和借鉴了别人的代码将功能实现了。现在整理一下代码,方便自己以后学习具体思路如下
重写ListView的onInterceptTouchEvent方法进行控件的touch事件拦截
这个方法的作用很简单:当我们摁下的如果是可拖拽的图标,那么进行初始化该Item的映像试视图。
同时在拖动中判断拖动的距离,具体查看下面代码。不是则不进行处理。代码如下(代码注释非常清楚,如果还有不懂的地方可以在下面留言,我们一起讨论,或者下载源码,自己好好研究一下)
package com.pengguichu.testdraglistview.listview;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;
import com.pengguichu.testdraglistview.R;
import com.pengguichu.testdraglistview.adapter.DragListAdapter;
import com.pengguichu.testdraglistview.entiy.DragItemInfo;
/**
* 自定义可拖动Item排序ListView
*
* @author guichupeng
*
*/
@SuppressLint({ "NewApi", "HandlerLeak" })
public class DragListView extends ListView {
private ImageView mDragImageView;// 被拖拽的项(item),其实就是一个ImageView
private int mStartPosition;// 手指拖动项原始在列表中的位置
private int mDragPosition;// 手指点击准备拖动的时候,当前拖动项在列表中的位置
private int mLastPosition;// 手指点击准备拖动的时候,当前拖动项在列表中的位置
private int mDragPoint;// 在当前数据项中的位置
private int mDragOffset;// 当前视图和屏幕的距离(这里只使用了y方向上)
private int mUpScrollBounce;// 拖动的时候,开始向上滚动的边界
private int mDownScrollBounce;// 拖动的时候,开始向下滚动的边界
private final static int mStep = 1;// ListView 滑动步伐
private int mCurrentStep;// 当前步伐
private DragItemInfo mDragItemInfo;// 用于存放Item信息的对象
private int mItemVerticalSpacing = 0;// Item垂直区域空间
private int mHoldPosition;// 标记最后停靠的Position
/** windows窗口控制类 */
private WindowManager mWindowManager;
/** 用于控制拖拽项的显示的参数 */
private WindowManager.LayoutParams mWindowParams;
/** 停止状态 */
public static final int MSG_DRAG_STOP = 0x1001;
/** 移动状态 */
public static final int MSG_DRAG_MOVE = 0x1002;
/** 动画时长(一个动画的耗时) */
private static final int ANIMATION_DURATION = 200;
/** 标识是否上锁 */
private boolean isLock;
/** 标识是否处于移动状态 */
private boolean isMoving = false;
/** 是否拖动Item */
private boolean isDragItemMoving = false;
/** 标识是否获取到间距 */
private boolean bHasGetSapcing = false;
public DragListView(Context context, AttributeSet attrs) {
super(context, attrs);
setLayerType(View.LAYER_TYPE_HARDWARE, null);
mDragItemInfo = new DragItemInfo();
init();
}
/**
* 初始化
*/
private void init() {
mWindowManager = (WindowManager) getContext()
.getSystemService("window");
}
/**
* 接收消息并完成对应动作
*/
Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case MSG_DRAG_STOP:// 停止
stopDrag();
onDrop(msg.arg1);
break;
case MSG_DRAG_MOVE:// 移动
onDrag(msg.arg1);
break;
}
};
};
/**
* 获取间距--获取上下滚动间距
*/
private void getSpacing() {
bHasGetSapcing = true;
mUpScrollBounce = getHeight() / 3;// 取得向上滚动的边际,大概为该控件的1/3
mDownScrollBounce = getHeight() * 2 / 3;// 取得向下滚动的边际,大概为该控件的2/3
int[] firstTempLocation = new int[2];
int[] secondTempLocation = new int[2];
ViewGroup firstItemView = (ViewGroup) getChildAt(0);// 第一行
ViewGroup secondItemView = (ViewGroup) getChildAt(1);// 第二行
if (firstItemView != null) {
firstItemView.getLocationOnScreen(firstTempLocation);
} else {
return;
}
if (secondItemView != null) {
secondItemView.getLocationOnScreen(secondTempLocation);
mItemVerticalSpacing = Math.abs(secondTempLocation[1]
- firstTempLocation[1]);
} else {
return;
}
}
/***
* touch事件拦截 在这里我进行相应拦截,
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 按下
if (ev.getAction() == MotionEvent.ACTION_DOWN && !isLock && !isMoving
&& !isDragItemMoving) {
int x = (int) ev.getX();// 获取相对与ListView的x坐标
int y = (int) ev.getY();// 获取相应与ListView的y坐标
mLastPosition = mStartPosition = mDragPosition = pointToPosition(x,
y);
// 无效不进行处理
if (mDragPosition == AdapterView.INVALID_POSITION) {
return super.onInterceptTouchEvent(ev);
}
if (false == bHasGetSapcing) {
getSpacing();
}
// 获取当前位置的视图(可见状态)
ViewGroup dragger = (ViewGroup) getChildAt(mDragPosition
- getFirstVisiblePosition());
DragListAdapter adapter = (DragListAdapter) getAdapter();
mDragItemInfo.obj = adapter.getItem(mDragPosition
- getFirstVisiblePosition());
// 获取到的dragPoint其实就是在你点击指定item项中的高度.
mDragPoint = y - dragger.getTop();
// 这个值是固定的:其实就是ListView这个控件与屏幕最顶部的距离(一般为标题栏+状态栏).
mDragOffset = (int) (ev.getRawY() - y);
// 获取可拖拽的图标
View draggerIcon = dragger.findViewById(R.id.drag_item_image);
if (draggerIcon.getVisibility() == View.VISIBLE) {// 只有在按钮为可见的情况下才允许移动
// x > dragger.getLeft() - 20这句话为了更好的触摸(-20可以省略)
if (draggerIcon != null && x > draggerIcon.getLeft() - 20) {
dragger.destroyDrawingCache();
dragger.setDrawingCacheEnabled(true);// 开启cache.
dragger.setBackgroundColor(0xffefefef);
Bitmap bm = Bitmap.createBitmap(dragger
.getDrawingCache(true));// 根据cache创建一个新的bitmap对象.
hideDropItem();
adapter.setInvisiblePosition(mStartPosition);
adapter.notifyDataSetChanged();
startDrag(bm, y);// 初始化影像
isMoving = false;
adapter.copyList();
}
}
}
return super.onInterceptTouchEvent(ev);
}
/**
* 获取依个缩放动画
*
* @return
*/
public Animation getScaleAnimation() {
Animation scaleAnimation = new ScaleAnimation(0.0f, 0.0f, 0.0f, 0.0f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
scaleAnimation.setFillAfter(true);
return scaleAnimation;
}
/**
* 隐藏下降的Item
*/
private void hideDropItem() {
final DragListAdapter adapter = (DragListAdapter) this.getAdapter();
adapter.showDropItem(false);
}
/**
* 触摸事件处理
*/
@Override
public boolean onTouchEvent(MotionEvent ev) {
// item的view不为空,且获取的dragPosition有效
if (mDragImageView != null && mDragPosition != INVALID_POSITION
&& !isLock) {
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);
itemMoveAnimation(moveY);
break;
case MotionEvent.ACTION_DOWN:
break;
}
return true;// 取消ListView滑动.
}
return super.onTouchEvent(ev);
}
/**
* 是否为相同方向拖动的标记
*/
private boolean isSameDragDirection = true;
/**
* 移动方向的标记,-1为默认值,0表示向下移动,1表示向上移动
*/
private int lastFlag = -1;
private int mFirstVisiblePosition, mLastVisiblePosition;// 第一个、最后一个的位置
private int turnUpPosition, turnDownPosition;// 向上、下的位置
/**
* 动态改变Item内容
*
* @param last
* // 最后一项的位置
* @param current
* // 当前位置
*/
private void onChangeCopy(int last, int current) {
DragListAdapter adapter = (DragListAdapter) getAdapter();
if (last != current) {// 判断是否移动到最后一项
adapter.exchangeCopy(last, current);
}
}
/**
* Item移动动画
*
* @param y
*/
private void itemMoveAnimation(int y) {
final DragListAdapter adapter = (DragListAdapter) getAdapter();
int tempPosition = pointToPosition(0, y);
if (tempPosition == INVALID_POSITION || tempPosition == mLastPosition) {
return;
}
mFirstVisiblePosition = getFirstVisiblePosition();
mDragPosition = tempPosition;
onChangeCopy(mLastPosition, mDragPosition);
int MoveNum = tempPosition - mLastPosition;// 计算移动项--移动距离
int count = Math.abs(MoveNum);
for (int i = 1; i <= count; i++) {
int xAbsOffset, yAbsOffset;
// 向下拖动
if (MoveNum > 0) {
if (lastFlag == -1) {
lastFlag = 0;
isSameDragDirection = true;
}
if (lastFlag == 1) {
turnUpPosition = tempPosition;
lastFlag = 0;
isSameDragDirection = !isSameDragDirection;
}
if (isSameDragDirection) {
mHoldPosition = mLastPosition + 1;
} else {
if (mStartPosition < tempPosition) {
mHoldPosition = mLastPosition + 1;
isSameDragDirection = !isSameDragDirection;
} else {
mHoldPosition = mLastPosition;
}
}
xAbsOffset = 0;
yAbsOffset = -mItemVerticalSpacing;
mLastPosition++;
} else {// 向上拖动
if (lastFlag == -1) {
lastFlag = 1;
isSameDragDirection = true;
}
if (lastFlag == 0) {
turnDownPosition = tempPosition;
lastFlag = 1;
isSameDragDirection = !isSameDragDirection;
}
if (isSameDragDirection) {
mHoldPosition = mLastPosition - 1;
} else {
if (mStartPosition > tempPosition) {
mHoldPosition = mLastPosition - 1;
isSameDragDirection = !isSameDragDirection;
} else {
mHoldPosition = mLastPosition;
}
}
xAbsOffset = 0;
yAbsOffset = mItemVerticalSpacing;
mLastPosition--;
}
adapter.setHeight(mItemVerticalSpacing);
adapter.setIsSameDragDirection(isSameDragDirection);
adapter.setLastFlag(lastFlag);
ViewGroup moveView = (ViewGroup) getChildAt(mHoldPosition
- getFirstVisiblePosition());
Animation animation;
if (isSameDragDirection) {// 相同方向拖动
animation = getFromSelfAnimation(xAbsOffset, yAbsOffset);
} else {// 不相同方向拖动
animation = getToSelfAnimation(xAbsOffset, -yAbsOffset);
}
// 启用对应的动画
moveView.startAnimation(animation);
}
}
private void onDrop(int x, int y) {
final DragListAdapter adapter = (DragListAdapter) getAdapter();
adapter.setInvisiblePosition(-1);
adapter.showDropItem(true);
adapter.notifyDataSetChanged();
}
/**
* 准备拖动,初始化拖动项的图像
*
* @param bm
* @param y
*/
private void startDrag(Bitmap bm, int y) {
/***
* 初始化window.
*/
mWindowParams = new WindowManager.LayoutParams();
mWindowParams.gravity = Gravity.TOP;
mWindowParams.x = 0;
mWindowParams.y = y - mDragPoint + mDragOffset;
mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
mWindowParams.height = 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;// 窗口占满整个屏幕,忽略周围的装饰边框(例如状态栏)。此窗口需考虑到装饰边框的内容。
// windowParams.format = PixelFormat.TRANSLUCENT;// 默认为不透明,这里设成透明效果.
mWindowParams.windowAnimations = 0;// 窗口所使用的动画设置
mWindowParams.alpha = 0.8f;
mWindowParams.format = PixelFormat.TRANSLUCENT;
ImageView imageView = new ImageView(getContext());
imageView.setImageBitmap(bm);
mWindowManager.addView(imageView, mWindowParams);
mDragImageView = imageView;
}
/**
* 拖动执行,在Move方法中执行
*
* @param y
*/
public void onDrag(int y) {
int drag_top = y - mDragPoint;// 拖拽view的top值不能<0,否则则出界.
if (mDragImageView != null && drag_top >= 0) {
mWindowParams.alpha = 1.0f;
mWindowParams.y = y - mDragPoint + mDragOffset;
mWindowManager.updateViewLayout(mDragImageView, mWindowParams);// 时时移动.
}
doScroller(y);// listview移动.
}
/***
* ListView的移动.
* 要明白移动原理:当我移动到下端的时候,ListView向上滑动,当我移动到上端的时候,ListView要向下滑动。正好和实际的相反.
*
*/
public void doScroller(int y) {
// ListView需要下滑
if (y < mUpScrollBounce) {
mCurrentStep = mStep + (mUpScrollBounce - y) / 10;// 时时步伐
}// ListView需要上滑
else if (y > mDownScrollBounce) {
mCurrentStep = -(mStep + (y - mDownScrollBounce)) / 10;// 时时步伐
} else {
mCurrentStep = 0;
}
// 获取你拖拽滑动到位置及显示item相应的view上(注:可显示部分)(position)
View view = getChildAt(mDragPosition - getFirstVisiblePosition());
// 真正滚动的方法setSelectionFromTop()
setSelectionFromTop(mDragPosition, view.getTop() + mCurrentStep);
}
/**
* 停止拖动,删除影像
*/
public void stopDrag() {
isMoving = false;
if (mDragImageView != null) {
mWindowManager.removeView(mDragImageView);
mDragImageView = null;
}
isSameDragDirection = true;
lastFlag = -1;
DragListAdapter adapter = (DragListAdapter) getAdapter();
adapter.setLastFlag(lastFlag);
adapter.pastList();
}
/**
* 拖动放下的时候
*
* @param y
*/
public void onDrop(int y) {
onDrop(0, y);
}
/**
* 获取自身出现的动画
*
* @param x
* @param y
* @return
*/
private Animation getFromSelfAnimation(int x, int y) {
TranslateAnimation translateAnimation = new TranslateAnimation(
Animation.RELATIVE_TO_SELF, 0, Animation.ABSOLUTE, x,
Animation.RELATIVE_TO_SELF, 0, Animation.ABSOLUTE, y);
translateAnimation
.setInterpolator(new AccelerateDecelerateInterpolator());
translateAnimation.setFillAfter(true);
translateAnimation.setDuration(ANIMATION_DURATION);
translateAnimation.setInterpolator(new AccelerateInterpolator());
return translateAnimation;
}
/**
* 获取自身离开的动画
*
* @param x
* @param y
* @return
*/
private Animation getToSelfAnimation(int x, int y) {
TranslateAnimation translateAnimation = new TranslateAnimation(
Animation.ABSOLUTE, x, Animation.RELATIVE_TO_SELF, 0,
Animation.ABSOLUTE, y, Animation.RELATIVE_TO_SELF, 0);
translateAnimation
.setInterpolator(new AccelerateDecelerateInterpolator());
translateAnimation.setFillAfter(true);
translateAnimation.setDuration(ANIMATION_DURATION);
translateAnimation.setInterpolator(new AccelerateInterpolator());
return translateAnimation;
}
}
package com.pengguichu.testdraglistview.adapter;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.BaseAdapter;
import android.widget.TextView;
import com.pengguichu.testdraglistview.R;
/**
* 自定义可拖拽ListView适配器
* @author guichupeng 下午1:15:31
*/
public class DragListAdapter extends BaseAdapter {
private List mDataList;// 标题数组
private Context mContext;
/**
* DragListAdapter构造方法
*
* @param context
* // 上下文对象
* @param dataList
* // 数据集合
*/
public DragListAdapter(Context context, ArrayList dataList) {
this.mContext = context;
this.mDataList = dataList;
}
/**
* 设置是否显示下降的Item
*
* @param showItem
*/
public void showDropItem(boolean showItem) {
this.mShowItem = showItem;
}
/**
* 设置不可见项的位置标记
*
* @param position
*/
public void setInvisiblePosition(int position) {
mInvisilePosition = position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
/***
* 在这里尽可能每次都进行实例化新的,这样在拖拽ListView的时候不会出现错乱.
* 具体原因不明,不过这样经过测试,目前没有发现错乱。虽说效率不高,但是做拖拽LisView足够了。
*/
convertView = LayoutInflater.from(mContext).inflate(
R.layout.drag_list_item, null);
initItemView(position, convertView);
TextView titleTv = (TextView) convertView
.findViewById(R.id.drag_item_title_tv);
titleTv.setText(mDataList.get(position));
if (isChanged) {// 判断是否发生了改变
if (position == mInvisilePosition) {
if (!mShowItem) {// 在拖拽过程不允许显示的状态下,设置Item内容隐藏
// 因为item背景为白色,故而在这里要设置为全透明色防止有白色遮挡问题(向上拖拽)
convertView.findViewById(R.id.drag_item_layout)
.setBackgroundColor(0x0000000000);
// 隐藏Item上面的内容
int vis = View.INVISIBLE;
convertView.findViewById(R.id.drag_item_image)
.setVisibility(vis);
convertView.findViewById(R.id.drag_item_close_layout)
.setVisibility(vis);
titleTv.setVisibility(vis);
}
}
if (mLastFlag != -1) {
if (mLastFlag == 1) {
if (position > mInvisilePosition) {
Animation animation;
animation = getFromSelfAnimation(0, -mHeight);
convertView.startAnimation(animation);
}
} else if (mLastFlag == 0) {
if (position < mInvisilePosition) {
Animation animation;
animation = getFromSelfAnimation(0, mHeight);
convertView.startAnimation(animation);
}
}
}
}
return convertView;
}
/**
*
* 初始化Item视图
*
* @param convertView
*/
private void initItemView(final int position, final View convertView) {
if (convertView != null) {
// 设置对应的监听
convertView.findViewById(R.id.drag_item_close_layout)
.setOnClickListener(new OnClickListener() {// 删除
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
removeItem(position);
}
});
}
}
private int mInvisilePosition = -1;// 用来标记不可见Item的位置
private boolean isChanged = true;// 标识是否发生改变
private boolean mShowItem = false;// 标识是否显示拖拽Item的内容
/***
* 动态修改ListView的方位.
*
* @param startPosition
* 点击移动的position
* @param endPosition
* 松开时候的position
*/
public void exchange(int startPosition, int endPosition) {
Object startObject = getItem(startPosition);
if (startPosition < endPosition) {
mDataList.add(endPosition + 1, (String) startObject);
mDataList.remove(startPosition);
} else {
mDataList.add(endPosition, (String) startObject);
mDataList.remove(startPosition + 1);
}
isChanged = true;
}
/**
* 动态修改Item内容
*
* @param startPosition
* // 开始的位置
* @param endPosition
* // 当前停留的位置
*/
public void exchangeCopy(int startPosition, int endPosition) {
Object startObject = getCopyItem(startPosition);
if (startPosition < endPosition) {// 向下移动
mCopyList.add(endPosition + 1, (String) startObject);
mCopyList.remove(startPosition);
} else {// 向上拖动或者不动
mCopyList.add(endPosition, (String) startObject);
mCopyList.remove(startPosition + 1);
}
isChanged = true;
}
/**
* 删除指定的Item
*
* @param pos
* // 要删除的下标
*/
private void removeItem(int pos) {
if (mDataList != null && mDataList.size() > pos) {
mDataList.remove(pos);
this.notifyDataSetChanged();
}
}
/**
* 获取镜像(拖拽)Item项
*
* @param position
* @return
*/
public Object getCopyItem(int position) {
return mCopyList.get(position);
}
/**
* 获取Item总数
*/
@Override
public int getCount() {
return mDataList.size();
}
/**
* 获取ListView中Item项
*/
@Override
public Object getItem(int position) {
return mDataList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
/**
* 添加拖动项
*
* @param start
* // 要进行添加的位置
* @param obj
*/
public void addDragItem(int start, Object obj) {
mDataList.remove(start);// 删除该项
mDataList.add(start, (String) obj);// 添加删除项
}
private ArrayList mCopyList = new ArrayList();
public void copyList() {
mCopyList.clear();
for (String str : mDataList) {
mCopyList.add(str);
}
}
public void pastList() {
mDataList.clear();
for (String str : mCopyList) {
mDataList.add(str);
}
}
private boolean isSameDragDirection = true;// 是否为相同方向拖动的标记
private int mLastFlag = -1;
private int mHeight;
private int mDragPosition = -1;
/**
* 设置是否为相同方向拖动的标记
*
* @param value
*/
public void setIsSameDragDirection(boolean value) {
isSameDragDirection = value;
}
/**
* 设置拖动方向标记
*
* @param flag
*/
public void setLastFlag(int flag) {
mLastFlag = flag;
}
/**
* 设置高度
*
* @param value
*/
public void setHeight(int value) {
mHeight = value;
}
/**
* 设置当前拖动位置
*
* @param position
*/
public void setCurrentDragPosition(int position) {
mDragPosition = position;
}
/**
* 从自身出现的动画
*
* @param x
* @param y
* @return
*/
private Animation getFromSelfAnimation(int x, int y) {
TranslateAnimation translateAnimation = new TranslateAnimation(
Animation.RELATIVE_TO_SELF, 0, Animation.ABSOLUTE, x,
Animation.RELATIVE_TO_SELF, 0, Animation.ABSOLUTE, y);
translateAnimation
.setInterpolator(new AccelerateDecelerateInterpolator());
translateAnimation.setFillAfter(true);
translateAnimation.setDuration(100);
translateAnimation.setInterpolator(new AccelerateInterpolator());
return translateAnimation;
}
}
方法注释很详细,大家应该能弄明白,不行的话留言讨论或者 源码下载连接自己研究,展示图如下