Android DragAndDrop API 拖拽效果 交换ListView的Item值

前言

Android系统自API Level11开始添加了关于控件拖拽的相关API,可以方便的实现控件的一些拖拽效果,而且比自己用Touch事件写的效果更好。下面就来看下DragAndDrop吧。

使用Android的DragAndDrop框架,我们可以方便的在当前布局中用拖拽的形式实现两个View之间数据的互换。DragAndDrop框架包括一个拖拽事件的类,拖拽监听器,以及一些帮助方法和类。

尽管DragAndDrop主要是为了数据移动而设计,但是我们也可用他做别的UI处理。举个例子,我们可以用它来做一个APP,功能是当拖拽一个色调的图标a经过另一个色调的图标b时,将这两种色调混合。

概述

一个拖拽操作开始于当用户在手机屏幕上做出一些被我们定义为开始拖拽数据的手势。作为这些手势的回应,我们的app告诉Android系统说拖拽开始啦。Android系统会回调我们的app方法来获取被拖拽的展现数据。当用户的手指移动着这些展现(实际是被拖拽的控件影子)经过当前布局,系统就会发出拖拽事件给拖拽事件监听器对象和拖拽事件的回调方法将布局中的视图View关联起来。一旦用户手指松开,系统会终止拖拽操作。

我们创造一个实现了View.OnDragListener接口的类的拖拽事件监听器对象(Listener),通过View.setOnDragListener给一个View对象设置拖拽监听器,可将这个对象当做参数传递进去。每个VIew对象也有一个onDragEvent()的回调方法。当开始一个拖拽操作的时候,会将移动中的数据和描述这些数据的原始数据传递给系统。拖拽过程中,系统会给布局中的每个View的拖拽事件监听器或者回调方法发出拖拽事件,监听器或者回调方法可以根据原始数据来决定当手指松开的时候他们是否接收这些数据。如果用户将数据放到另一个View之上,并且这个View的监听器或者回调方法事先已经告诉系统他会接收来自拖拽的数据,那么系统就会将数据放在拖拽事件里发给这个View的监听器或者回调方法。

app一旦调用startDrag()方法就告诉了系统开始一个拖拽操作,发送拖拽数据和拖拽事件event。布局里的任何View都可以调用startDrag()方法,系统只会用布局里的View对象去访问全局设置。一旦调用startDrag()方法,剩下的就是处理系统发给View对象的拖拽事件event了。

Drag/Drop的处理

Drag/Drop的流程基本有以下四步:

1. Started

为了响应用户开始拖拽的手势,app调用startDrag()方法告诉系统开始一个拖拽流程。startDrag()的参数包含了被拖拽对象的数据,以及这些数据的元数据,和绘制拖拽影子的回调方法。

系统首先会响应回调获取一个拖拽影子,然后展示在设备上。

下一步,系统会发送一个ACTION_DRAG_STARTED类型的拖拽事件给当前布局的所有View的拖拽监听器。为了能继续接收拖拽事件,以及可能的松开事件,拖拽事件监听器必须返回一个true。这个在系统注册监听器,只有注册监听器才能继续接收拖拽事件,在这点上,监听器可以改变它们的View对象的外观表明监听器也可以接收一个手指松开的事件。

如果手指监听器返回一个false,他就不会再接收到当前拖拽操作的拖拽事件直到系统发送一个ACTION_DRAG_ENDED类型的拖拽事件。发送false就表示监听器告诉系统,他对本次的拖拽操作已经不感兴趣,也不愿接收拖拽数据。

2. Continuing

用户继续拖拽,当拖拽的影子到达一个View视图内部的时候,系统会发出一个或者多个拖拽事件到这个View的拖拽事件监听器(如果这个View注册了监听器),监听器可以选择改变这个View的外观来响应这些事件。举个例子,如果拖拽事件表明拖拽影子已经进入了View的盒子模型内,这个时候的事件类型是ACTION_DRAG_ENTERED,这个View可以高亮自己。

3. Dropped

当用户手指在一个View的范围内松开拖拽影子的时候可以接受拖拽数据,这时这个View的监听器会收到类型为ACTION_DROP的事件。拖拽事件包括开始拖拽操作startDrag()的时候传递给系统的拖拽数据。如果代码成功接收了drop事件,监听器返回true。

要说明的是,这个过程只会发生在控件已经注册拖拽事件监听器接收拖拽事件并且手指在这个控件的范围内松开的情况下。如果用户在其他任何一种情况下释放拖拽,都不会有ACTION_DROP类型的事件发出。

4. ENDED

在用户松开拖拽影子后,在系统发送ACTION_DROP类型的拖拽事件(如果有必要)后,系统会发出一个ACTION_DRAG_ENDED类型的拖拽事件表明此次拖拽操作结束。不管用户在哪里释放这都表明本次拖拽操作结束。只要注册了监听器接收拖拽事件都会收到这个事件,即使已经接受了ACTION_DROP事件。

拖拽事件监听器和回调方法

一个View要么通过注册一个实现了View.OnDragListner接口的监听器,要么通过他的回调方法onDragEvent(DragEvent)来接收拖拽事件,当系统调用回调方法或者监听器的方法的时候,系统会传递给他们一个DragEvent类型的对象。

我们大多数情况下都会使用监听器而不是直接用回调方法,当我们设计UI的时候,我们不会经常继承View类,但是使用回调方法的办法将会为了覆盖这个方法而强制我们这么做。相比较来说,我们可以实现一个监听器类然后把它应用到不同的View对象中。也可以将他写成一个匿名内部类,然后用setOnDragListener方法将他赋给View对象。

当然一个View对象可以同时具有监听器和回调方法,这种情况下,系统将会首先调用监听器,如果监听器返回false,系统才会接下来调用回调方法。

onDragEvent(Dragevent)方法和View.OnDragListner的结合与onTouchEvent()和View.OnTouchListener关于触摸事件的结合相似。

Drag events

系统以DragEvent对象的形式发送拖拽事件。这个对象包含事件动作类型,告诉监听器拖拽过程中发生了什么。这个对象中还包含其他依赖于事件动作类型的数据。监听器可以通过调用getAction()方法来获取事件类型,DragEvent一共定义了六种类型如下

而下图则表示了监听器各个阶段,获取各种数据的能力

上图中的×符号表示有能力,可以获取到相应数据。

drag shadow

在拖拽操作期间,系统会展现一个图片来跟随手势移动,这个图片形象的表示数据在移动,对于其他操作,这个图片会展现出一些拖拽操作的方面。

这个图片被称作拖拽影子。我们可以通过声明一个View.DragShadowBuilder对象来创建这个影子,然后当app调用startDrag()的时候将他传递给系统。作为对startDrag()方法响应的一部分,系统会调用我们在View.DragShadowBuilder中定义的回调方法来获取一个拖拽影子。

View.DragShadowBuilder有两个构造方法:

View.DragShadowBuilder(View)

这个构造方法接受任何View对象,将View对象存储在View.DragShadowBuilder类中,所以只要我们调用这个构造方法那么在执行回调的时候我们都可以访问到这个View,并不需要一定和用户选择开始拖拽的View做关联。如果使用这个构造方法,不用非要继承View.DragShadowBuilder类或者重写他的方法。默认情况下,我们将会得到一个在用户触摸位置的下方正中间和我们传递进去当做参数的View外观相同的拖拽影子。

View.DragShadowBuilder()

如果调用这个构造方法,View.DragShadowBuilder中的View变量将被置为空,也不能继承View.DragShadowBuilder类或者复写他的方法,我们将会获得一个不可见的拖拽影子,系统也不会报错。

View.DragShadowBuilder有两个方法:

onProviceShadowMetrics()

app调用startDrag()后这个方法会直接被调用,用它来向系统发送拖拽影子的尺寸和触摸点。这个方法有两个参数:

dimensions:

一个Point对象,其中x代表影子的宽度,y代表影子的高度

touch_point:

一个Point对象,触摸点就是在拖拽过程中用户手指下在影子内部的那部分区域。x代表了他的X坐标,y代表了他的Y坐标。

onDrawShadow:

在调用onProvideShadowMetrics()方法之后系统直接调用onDrawShadow()方法来获得影子本身。这个方法有一个参数就是Canvas类型的对象,这个对象是系统用我们提供的onProvideShadowMetrics()方法中的参数来构造的,在这上面来绘制影子。

为了更好的性能,我们应该保持拖拽影子的尺寸尽量的小。对于单选元素,可能需要使用一个图标,对于多选的情况,可能需要使用缓存的多个图标而不是用铺满整屏的图片。

下面就真正通过代码来演示一个简单ListView交换item数据的Demo,利用上面提到的API和步骤来实现一个拖拽数据交换的例子。

新建项目DragAndDropDemo:

布局文件里只有一个ListView,简单的填充了几个字符串类型的item:

设置item的长按事件触发拖拽效果,并将被拖拽的item文本数据放入了所要发出的事件中,通过view.startDrag()开始拖拽。

public class MainActivity extends Activity {
private myDragEventListener mDragListen;
private ListView lv_main;
private String[] data = new String[]{"a","b","c","d","e","f","g"};
private DragAdapter adapter; 
int currentPos;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		lv_main = (ListView) findViewById(R.id.lv_main);
		adapter = new DragAdapter();
		lv_main.setAdapter(adapter);
		lv_main.setOnItemLongClickListener(new OnItemLongClickListener() {
			@Override
			public boolean onItemLongClick(AdapterView<?> parent, View view,
					int position, long id) {
				// Create a new ClipData.Item from the ImageView object's tag
				String origData = data[position];
			    ClipData.Item item = new ClipData.Item(origData);
			    ClipData.Item item1 = new ClipData.Item(position+"");
			    ClipData dragData = new ClipData(origData,new String[] {
			        ClipDescription.MIMETYPE_TEXT_PLAIN },item);
			    dragData.addItem(item1);
			    View.DragShadowBuilder myShadow = new MyDragShadowBuilder(view);
			    view.startDrag(dragData,  // the data to be dragged
			                        myShadow,  // the drag shadow builder
			                        null,      // no need to use local data
			                        0          // flags (not currently used, set to 0)
			            );
			            return true;
			}
		});
		
		mDragListen = new myDragEventListener();
	}
class DragAdapter extends BaseAdapter{
		
		@Override
		public int getCount() {
			return data.length;
		}

		@Override
		public Object getItem(int position) {
			return data[position];
		}

		@Override
		public long getItemId(int position) {
			return position;
		}

		@Override
		public View getView(final int position, View convertView, ViewGroup parent) {
				final TextView tv = new TextView(getApplicationContext());
				tv.setDrawingCacheEnabled(true);
				tv.buildDrawingCache();
				tv.setOnDragListener(new myDragEventListener());
				tv.setTag(position);
				tv.setGravity(Gravity.CENTER);
				tv.setTextColor(Color.BLACK);
				tv.setBackgroundColor(Color.LTGRAY);
				tv.setPadding(0, 60, 0, 60);
				LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
				tv.setLayoutParams(params);
				tv.setText(data[position]);
				tv.setOnDragListener(mDragListen);
				return tv;
		}
		
	}

上面可以看到,adapter中的getView()方法中对每一个TextView都绑定了一个DragListener,业务逻辑是可以交换任意两个item的值,那么每一个item都应该注册这个监听器去监听拖拽事件和接收其传递的数据,那么下面我们来定义这个监听器:

protected class myDragEventListener implements View.OnDragListener {
	    public boolean onDrag(View v, DragEvent event) {
	        final int action = event.getAction();
	        switch(action) {
	            case DragEvent.ACTION_DRAG_STARTED:
	                if (event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
	                    return true;
	                }
	                return false;
	            case DragEvent.ACTION_DRAG_ENTERED:
	                v.invalidate();
	                return true;
	            case DragEvent.ACTION_DRAG_LOCATION:
	                return true;
	            case DragEvent.ACTION_DRAG_EXITED:
	                v.invalidate();
	                return true;
	            case DragEvent.ACTION_DROP:
	                ClipData.Item item = event.getClipData().getItemAt(0);
	                ClipData.Item item2 = event.getClipData().getItemAt(1);
	                int pos = Integer.valueOf(item2.getText().toString());
	                String dragData = item.getText().toString();
	                int dragedPos = (Integer) v.getTag();
	                data[pos] = data[dragedPos];
	                data[dragedPos] = dragData;
	                return true;
	            case DragEvent.ACTION_DRAG_ENDED:
	            	adapter.notifyDataSetChanged();
	                return false;
	            default:
	            	Toast.makeText(getApplicationContext(), "未知手势", Toast.LENGTH_LONG).show();
	                break;
	        }
	        return false;
	    }
	};

好了,拖拽交换的主要逻辑到这儿就写完了,效果如下:

到这里似乎还少了什么,并不能出现我们期望的效果,那是因为拖拽的影子还没有处理,下面来处理拖拽时候的影子:

public class MyDragShadowBuilder extends DragShadowBuilder {
	private static Drawable shadow;

	public MyDragShadowBuilder(View v) {
		super(v);
		bit = Bitmap.createBitmap(v.getDrawingCache());
		shadow = new ColorDrawable(Color.LTGRAY);
	}

	private int width, height;
	private Bitmap bit;

	@Override
	public void onProvideShadowMetrics(Point size, Point touch) {

		width = getView().getWidth() * 4 / 3;
		height = getView().getHeight() * 4 / 3;
		shadow.setBounds(0, 0, width, height);

		size.set(width, height);

		touch.set(width / 2, height / 2);
	}

	@Override
	public void onDrawShadow(Canvas canvas) {
		canvas.drawBitmap(bit, 0, 0, new Paint());
	}
}

由于Android
API的封装,拖拽影子的实现相当简单,就这么几行代码,其实就是获取传递进来的要拖拽的View的快照,这里通过getDrawingCache()来获取View的快照图片,需要注意的是需要在获取之前先通过setDrawingCacheEnabled(true)将其设置,否则会获取不到。

到这里就写完了,比自己用Touch事件要简单很多很多,而且基本没什么bug,下面附上源码:

DragAndDropDemo源码

如果感觉这篇内容对您有帮助,就请移到下方顺手点个赞吧,不胜感激2333.

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-13 08:58:23

Android DragAndDrop API 拖拽效果 交换ListView的Item值的相关文章

【Android】可拖拽排序的ListView

[Android]可拖拽排序的ListView 实现Item的拖拽排序效果 下载地址:http://www.devstore.cn/code/info/746.html 运行截图:

Android中GridView拖拽的效果

最 近看到联想,摩托罗拉等,手机launcher中有个效果,进入mainmenu后,里面的应用程序的图标可以拖来拖去,所以我也参照网上给的代码,写了 一个例子.还是很有趣的,实现的流畅度没有人家的那么好,我只是模仿这种效果,我写的这个拖拽是两个图标之间进行交换,所以,当从一行的某个位置,换到下 一行的另一列的时候,发现有好几个图标都改变位置了,因为是相邻两个交换位置,所以每经过相邻的图标的时候都改变位置.先弄个雏形,以后再更新优化. 转载请标明出处:http://blog.csdn.net/wd

自定义控件——可拖拽排序的ListView

前言 最经研究了一下拖拽排序的ListView,跟酷狗里的播放列表排序一样,但因为要添加自己特有的功能,所以研究了好长时间.一开始接触的是GitHub的开源项目--DragSortListView,实现的效果和流畅度都很棒.想根据他的代码自己写一个,但代码太多了,实现的好复杂,看别人的代码你懂的了,就去尝试寻找其他办法.最后还是找到了更简单的实现方法,虽然跟开源项目比要差一点,但对我来说可以了,最重要的是完全可以自定义. 实现的效果如下: 主要问题 如何根据触摸的位置确定是哪个条目? ListV

Createjs学习心得之使用EaselJs实现拖拽效果

寒假时跟着老师在做一个createjs+angularjs的html5应用app,因为在这之前完全没了解过createjs这个框架,所以在查找资料时发现,国外官网(http://www.createjs.com)API文档虽是详细,但对于一些英语不太好的童鞋(其实我也是一枚英语渣渣)就比较难理解了.而国内中文社区做的并不好,用户不多,资料翻译准确度不够.所以在这里,我就把我学习Createjs的一些心得体会向大家分享下: 一.什么是CreateJS? createjs是一个轻量级的javascr

Android图片的拖拽与缩放

Android图片的拖拽与缩放 2014年5月9日 我们在使用应用当中经常需要浏览图片,比如在微信当中,点击图片之后可以对图片进行缩放. 本博客介绍如何对图片进行拖拽和缩放,这首先要了解Android中的触摸机制了,在屏幕中有手指按下.手指抬起.手指移动还有多个手指触摸的动作.我们要实现对图片的拖拽和缩放就是要基于这些动作来进行逻辑处理. 图片的拖拽主要是计算手指开始的位置与当前手指的位置关系,来进行平移的,具体可以看代码. 图片的缩放就涉及到计算两点之间的距离来得到缩放比,调用矩阵方法来达到缩

DraggableView GridView项目拖拽效果

DraggableView GridView项目拖拽效果 DraggableView GridView项目拖拽效果, 自顶一个SampleGridContainer 集成FrameLayout实现DragController.IDragViewGroup ,里面主要提供了onDragStart ,onDragEnd,onMoveEvent等几个方法实现拖拽效果 运行效果: 相关代码 DraggableView GridView项目拖拽效果 PullDownListView高仿微信下拉眼睛出现动画

C#中实现控件拖拽效果(How to DragDrop Control in C#)

当产品间需要交互实现数据传递,或产品需要从外部导入文件时,通过控件拖拽来实现是个不错的选择.在UI上支持控件拖拽,可极大提升用户体验.拖拽本身并不神秘,它的本质实际是一个数据交换的过程.控件接受从其他地方来的数据,并进行处理.数据交换有多种方法,Windows中剪贴板可能就是用的最多,但最不被注意的一种方法.下面介绍用C#实现控件拖拽,并通过剪切板交换数据. 对于拖拽的对象,需要在MouseDown或ItemDrag中调用DoDragDrop,传递要拖拽的数据对象并触发拖拽.总的来说,当用户调用

简单的鼠标拖拽效果(原生js实现)

之前在聊天群里看到有人说面试的时候被问到了怎样实现一个拖拽效果,当时看到后在心里默默思考了下,结果发现好像我也写不出来啊.本着遇到一个解决一个的思想,就亲自敲了一个,看到张鑫旭大神写的代码,真的很厉害,多多学习了,(感觉随便搜一个关于前端方面的问题都能看到他的网站,真是太佩服了,写了那么多文章,十分感谢.)好了,接下来就进入正题了.想实现一个效果首先得明白其中的逻辑,知道了实现逻辑后,就可以码代码了.首先我实现的效果是: 鼠标按下后,才可以执行后续效果 鼠标已经按下,然后鼠标移动,需要拖拽的元素

js拖拽效果实现

<!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/xhtml" xml:lang="en"><head>    <meta