自定义ListView实现拖拽ListItem项交换位置(附源码)

写在前面的话

在上一篇实现了通过布局泵拿到不同布局为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和案例去学习,去进步,祝成功!

源码下载

时间: 2024-10-13 11:10:37

自定义ListView实现拖拽ListItem项交换位置(附源码)的相关文章

自定义ListView实现拖拽ListItem项交换位置(附源码)_Android

写在前面的话 在上一篇实现了通过布局泵拿到不同布局为listitem布局,然后实现联系人的ListView,这一章要做的是拖拽ListView的Item项,本章原理是在上一篇博客基础之上的,上一篇博客:自定义Adapter并通过布局泵LayoutInflater抓取layout模板编辑每一个item 实现效果图   说明 首先我们看到的上面这张图就是实现的效果图了.拖动之后数据项完成交换位置. 功能剖析 我们看到做的这个效果是一个拖拽ListView的Item项位置的功能,在布局方面还是用基于布

jQuery自定义图片缩放拖拽插件imageQ实现方法(附demo源码下载)_jquery

本文实例讲述了jQuery自定义图片缩放拖拽插件imageQ实现方法.分享给大家供大家参考,具体如下: 综合网上一些代码 自己写的一个图片缩放拖拽的小插件 /** * * <a href="http://lib.csdn.net/base/22" class='replace_word' title="jQuery知识库" target='_blank' style='color:#df3434; font-weight:bold;'>jQuery<

Android自定义多节点进度条显示的实现代码(附源码)

亲们里面的线段颜色和节点图标都是可以自定义的. 在没给大家分享实例代码之前,先给大家展示下效果图: main.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/rl_parent" xmlns:tools="http://schemas.android.com/tools" android:layou

自定义GridView并且实现拖拽(附源码)_Android

写在前面的话 本篇blog实现了GridView的拖拽功能.方法和上一篇自定义ListView实现拖拽ListItem项交换位置一个原理.只是在交换位置上记录了X轴的相关坐标,计算了X轴的相关变量. 实现效果图如下  说明: 本篇给出实现代码,但是不做任何说明.如需了解请看上一篇blog:自定义ListView实现拖拽ListItem项交换位置 文件代码: 1.MainActivity.java 复制代码 代码如下: package com.jay.draggridview; import ja

自定义GridView并且实现拖拽(附源码)

写在前面的话 本篇blog实现了GridView的拖拽功能.方法和上一篇自定义ListView实现拖拽ListItem项交换位置一个原理.只是在交换位置上记录了X轴的相关坐标,计算了X轴的相关变量. 实现效果图如下   说明: 本篇给出实现代码,但是不做任何说明.如需了解请看上一篇blog:自定义ListView实现拖拽ListItem项交换位置 文件代码: 1.MainActivity.java 复制代码 代码如下: package com.jay.draggridview; import j

JS仿iGoogle自定义首页模块拖拽特效的方法

 这篇文章主要介绍了JS仿iGoogle自定义首页模块拖拽特效的方法,实例分析了页面的布局及拖拽的实现技巧,非常具有实用价值,需要的朋友可以参考下     本文实例讲述了JS仿iGoogle自定义首页模块拖拽特效的方法.分享给大家供大家参考.具体实现方法如下:   代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xh

JS仿iGoogle自定义首页模块拖拽特效的方法_javascript技巧

本文实例讲述了JS仿iGoogle自定义首页模块拖拽特效的方法.分享给大家供大家参考.具体实现方法如下: 复制代码 代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999

5 drag拖拽 touchmove-怎么做到当拖拽已经到达指定位置取取消拖拽块的绑定

问题描述 怎么做到当拖拽已经到达指定位置取取消拖拽块的绑定 当touch事件触发之后 id="content"的块到达 top=0 的位置 而这个块中的内容长度大于一屏 想让这个块中的内容可以通过上下滚动完全显示出来 但是id="content"这个块依然出去drag事件中 怎么完成这种效果 求大神

jQuery拖拽排序插件制作拖拽排序效果(附源码下载)_jquery

使用jquery拖拽排序插件制作拖拽排序效果是一款非常实用的鼠标拖拽布局插件.效果图如下: 效果演示         源码下载 html代码: <h1>水平拖拽</h1> <div class="demo"> <div class="item item1"><span>1</span></div> <div class="item item2"><