自定义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和案例去学习,去进步,祝成功!

源码下载

(0)

相关推荐

  • Android 中ListView点击Item无响应问题的解决办法

    如果listitem里面包括button或者checkbox等控件,默认情况下listitem会失去焦点,导致无法响应item的事件,最常用的解决办法是在listitem的布局文件中设置descendantFocusability属性. item的布局文件: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.androi

  • Android开发之ListView实现Item局部刷新

    对于android中的ListView刷新机制,大多数的程序员都是很熟悉的,修改或者添加adapter中的数据源之后,然后调用notifyDataSetChanged()刷新ListView.在这种模式下,我们会在getView中,根据不同的数据源,让控件显示不同的内容.这种模式是最常见的刷新模式,当我们来回滑动ListView的时候,调用adapter的getView方法,然后listview对adapter返回的View进行绘制.这种模式下,View的显示内容或状态都记录在adapter里面

  • C# ListView双击Item事件

    复制代码 代码如下: private void listView右侧_MouseDoubleClick( object sender , MouseEventArgs e ) { ListViewHitTestInfo info = listView右侧.HitTest (e.X , e.Y); if( info.Item != null ) { MessageBox.Show (info.Item.Text); } }

  • Android ListView的item背景色设置和item点击无响应的解决方法

    下面讲解以下在使用listview时最常见的几个问题.1.如何改变item的背景色和按下颜色 listview默认情况下,item的背景色是黑色,在用户点击时是黄色的.如果需要修改为自定义的背景颜色,一般情况下有三种方法: 1)设置listSelector 2)在布局文件中设置item的background 3)在adapter的getview中设置 这三种方法都能达到改变item默认的背景色和按下颜色,下面来分别讲解,但是在这之前需要先写好selector.xml文件; 复制代码 代码如下:

  • Android 中ListView setOnItemClickListener点击无效原因分析

    前言 最近在做项目的过程中,在使用listview的时候遇到了设置item监听事件的时候在没有回调onItemClick 方法的问题.我的情况是在item中有一个Button按钮.所以不会回调.上百度找到了解决办法有两种,如下: 1.在checkbox.button对应的view处加android:focusable="false" 复制代码 代码如下: android:clickable="false" android:focusableInTouchMode=&

  • ListView点击Item展开菜单实现代码详解

    一.概述 ListView点击item显示菜单是要实现这样的效果: 需要实现的逻辑如下: 1)点击一个普通item,展开当前菜单,同时关闭其他菜单 2)点击一个已展开的菜单,隐藏当前菜单 3)将展开菜单滑到listview之外,再滑动回来,展开菜单状态不变 4)点击菜单中的按钮,能够根据不同item进行不同的处理 二.实现思路 1.UI布局上,对于这种每个listitem都包含动态显示菜单的场景,可以直接在listitem的xml布局里就包含两部分元素:item本身以及展开菜单 点击item的时

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

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

  • JS实现的简单拖拽购物车功能示例【附源码下载】

    本文实例讲述了JS实现的简单拖拽购物车功能.分享给大家供大家参考,具体如下: <html> <head> <meta charset="utf-8" /> <title>使用拖放API将商品拖入购物车</title> <style> body { font-size:12px } .liT{ border-bottom:solid 1px #ccc; background-color:#eee; font-weig

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

    本文实例讲述了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

  • 微信小程序使用radio显示单选项功能【附源码下载】

    本文实例讲述了微信小程序使用radio显示单选项功能.分享给大家供大家参考,具体如下: 1.效果展示 2.关键代码 ① index.wxml <radio-group bindchange="radiogroupBindchange"> <radio value="radio1">radio1</radio> <radio value="radio2">radio2</radio> &l

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

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

  • Java实现拖拽列表项的排序功能

    在一些允许用户自定义栏目顺序的app(如:凤凰新闻.网易云音乐等),我们可以方便地拖拽列表项来完成列表的重新排序,进而完成对栏目顺序的重排.这个功能很人性化,而实现起来其实很简单(甚至都不用写什么后台代码),只有三步. ①把冰箱门打开 首先,我们需要让冰箱的大门敞开,也就是允许我们进行拖拽的相关操作.以ListView为例,注意下面几个属性. <StackPanel> <ListView x:Name="list" AllowDrop="True"

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

    本文实例讲述了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

  • Android自定义View实现拖拽效果

    腾讯QQ有那种红点拖动效果,今天就来实现一个简单的自定义View拖动效果,再回到原处,并非完全仿QQ红点拖动. 先来看一下效果图 简单说一下实现步骤 1.创建一个类继承View 2.绘制出一个小球 3.重写onTouchEvent,来根据手指放下,移动,抬起,来控制小球 4.直接在布局中引用 先贴一张图看下View的坐标系 下面就贴一下代码,最后会给出源码 public class CustomView extends View { private int lastX; private int

  • Vue使用自定义指令实现拖拽行为实例分析

    本文实例讲述了Vue使用自定义指令实现拖拽行为.分享给大家供大家参考,具体如下: 需求 通过自定义指令的方式实现拖拽效果,预期的使用方式为: <div style="background: #f00; width: 200px; height: 200px;" v-drag> XXXX </div> 更重要的一个需求点: 拖拽元素内部的子元素可以自行阻止拖拽行为 比如: <div style="background: #f00; width: 2

随机推荐