第三部分:Android 应用程序接口指南---第二节:UI---第十章 拖放

第10章 拖放

使用Android的拖放框架,允许用户通过一个图形化的拖放动作,把数据从当前布局中的一个视图上转移到另一个视图上。这个框架包含了一个拖动事件类,拖动监听器和一些辅助的方法和类。

虽然这个框架主要是为了数据的移动而设计的,但是你可以将这些移动的数据提供给其他的UI操作使用。例如:你可以创建一个当用户把一个彩色图标拖到另一个彩色图标上时,将颜色混合起来的应用。接下来本文将描述关于这个拖放框架的数据移动的内容。

10.1 概述

当用户执行一些被当作是开始拖动数据的信号的手势时,一个拖放动作就开始了。作为回应,你的应用程序告诉系统拖动动作开始了。系统回调你的应用程序获取一个代表数据正在被拖动的图形。当用户的手指将这个代表图形(一个拖动阴影)移动到当前布局上时,系统分别发送拖动事件给拖动事件监听器对象,与布局中的View相联系的拖动事件回调方法。一旦用户释放这个拖动阴影,系统就结束拖动操作。你可以通过在一个类中实现View.OnDragListener,创建拖动事件监听器。然后通过视图对象的setOnDragListener()方法,为视图设置一个拖动事件监听器对象。每个视图对象都可以有一个onDragEvent()回调方法。注意:为了简便起见,在接下来的章节中,把程序接收拖动事件称为“拖动事件监听器(drag event listener)”,尽管事实上它也有可能是一个回调方法。当开始一个拖动,就同时包括了正在拖动的数据,以及描述这些数据的元数据作为系统回调的一部分。在拖动过程中,系统会发送拖动事件给布局中的每个视图的拖动事件监听器或回调方法。这些监听器或者回调方法可以使用元数据来决定他们是否想要接收那些被拖动的数据。如果用户将数据拖动到一个视图对象上,并且该视图对象的监听器或回调方法事先已经告诉过系统想要接收拖动的数据,那么系统就会把这些数据发送给拖动事件中的监听器或回调方法。

你的应用程序通过调用startDrag()方法告诉系统开始一个拖动,也就是告诉系统可以开始发送拖动事件了。startDrag()也会发送你正在拖动的数据。你可以调用当前布局中任意一个相关联视图的startDrag()方法。系统只会利用视图对象获得进入你布局中的全局设置的权限。一旦你的应用程序调用startDrag()方法,剩下的过程就是使用系统发送给布局中的视图对象的事件。

10.1.1拖放过程

拖放过程包括以下四个步骤或状态:

1. 开始

为了响应用户开始拖动的手势,你的应用程序通过调用 startDrag()方法告诉系统开始一个拖动动作。startDrag()的参数提供被拖动的数据,描述被拖动数据的元数据以及一个绘制拖动阴影的回调方法。系统的首次回应是通过回调你的应用程序去获得一个拖动阴影。然后将这个拖动阴影显示在设备上。接着,系统发送一个操作类型为DragEvent.ACTION_DRAG_STARTED的拖动事件给当前布局中的所有视图对象的拖动事件监听器。为了继续接收拖动事件,包括一个可能的拖动事件,拖动事件监听器必须返回true。这样就在系统中注册了一个监听器。只有被注册过的监听器才继续接收拖动事件。这时候,监听器也可以改变他们的视图对象的外观,来表明监听器可以接收一个拖放事件。如果拖动事件监听器返回值为false,那么在当前操作中就接收不到拖动事件,直到系统发送一个操作类型为DragEvent.ACTION_DRAG_ENDED的拖动事件。通过发送false,监听器告诉系统它对拖动操作不感兴趣,并且不想接收被拖动的数据。

2. 继续

用户继续拖动。当拖动阴影和视图对象的边界框相交,系统会发送一个或多个拖动事件给视图对象的拖动事件监听器(如果该事件监听器已经被注册为接收事件)。作为回应,监听器可以选择改变响应拖动事件的视图对象的外观。例如,如果事件表明阴影已经进入了视图的边界框(操作类型为DragEvent.ACTION_DRAG_ENDED),那么监听器就可以高亮视图以作出回应。

3. 释放

用户在可以接收数据的视图的边界框内释放拖动阴影。系统发送一个操作的类型为 ACTION_DROP拖动事件给视图对象的监听器。这个拖动事件包括调用startDrag()方法传给系统的数据。如果接收释放动作的代码执行成功,那么这个监听器会被期望返回true给系统。

注意,这一步只会在用户在监听器被注册为接收拖动时事件的视图的边界框内释放这个拖动阴影的情况下才会发生。如果用户在其他情况下释放这个拖动阴影,DragEvent.ACTION_DROP的拖动事件就不会被发送。

4. 终止

在用户释放拖动阴影并且系统发送出一个操作类型为ACTION_DROP的拖动事件(如果有必要的话)之后,系统发送出一个操作类型为DragEvent.ACTION_DRAG_ENDED的拖动事件来表明这个拖动操作已经结束了。不管用户在哪里释放这个拖动阴影,这个步骤都会发生。这个事件会发送给每一个被注册为接收拖动事件的监听器,即使这个监听器已经接收了DragEvent.ACTION_DROP事件。

10.1.2拖动事件监听器和回调方法

一个视图通过实现了View.OnDragListener的拖动事件监听器或通过它自己的View.onDragEvent(DragEvent)回调方法来接收拖动事件。当系统调用这个这个方法或监听器时,系统传递给他们一个DragEvent.DragEvent对象。

在大多数情况下,你可能会想要使用监听器。当你设计界面时,通常不会继承视图类,但使用回调方法时,为了要重写这个方法,就会迫使你去继承视图类。相比之下,你还可以实现一个监听器类,然后在几个不同的视图对象中使用它。你也可以将这个监听器类作为一个匿名内部类去实现。调用 setOnDragListener()这个方法,就可以为一个视图对象设置监听器。

视图对象可以同时有一个监听器和一个回调方法。如果在这种情况下,系统会首先调用监听器。除非监听器返回的是false,要不然系统不会去调用回调方法。

View.onDragEvent(DragEvent)方法和View.OnDragListener的结合跟触屏事件的View.onTouchEvent()与 View.OnTouchListener的结合是相似的。

10.1.3拖动事件

系统以DragEvent对象的形式发送一个拖动事件。这个对象包括一个告诉监听器在拖放事件中正在发生什么的事件类型。这个对象还包括其他依赖这个事件类型的数据。

监听器调用DragEvent.getAction()这个方法就可以获得这个事件类型。在DragEvent中,还有其他六个可能的变量,分别在表1中列出。

DragEvent对象还包括你的应用程序在调用startDrag()这个方法的时候要传递给系统的那些数据。在这些数据中,有些数据只是对某些特定的事件类型有效。对每个事件类型有效的数据都列在表10-2中。

getAction()的值

含义

ACTION_DRAG_STARTED

在应用程序调用 startDrag()并获得一个拖动阴影之后,视图对象的拖动事件监听器就会接收到这个事件类型的事件。

ACTION_DRAG_ENTERED

当拖动阴影刚刚进入视图的边界框范围时,视图的拖动事件监听器就会接收到这个action类型的事件。这是当拖动阴影进入视图的边界框范围时监听器所接收到的第一个事件操作类型。如果监听器还行继续为拖动阴影进入视图边界框范围之这个动作接收拖动事件的话,那么必须返回true给系统

ACTION_DRAG_LOCATION

当拖动阴影还在视图的边界框范围中,视图的拖动事件监听器就会在接收到ACTION_DRAG_ENTERED事件之后接收到这个操作类型的事件。

ACTION_DRAG_EXITED

当视图的拖动事件监听器接收到ACTION_DRAG_ENTERED这个事件,并且至少接收到一个ACTION_DRAG_LOCATION事件,那么在用户把拖动阴影移除视图的边界框范围之后,该监听器就会在接收到这个操作类型的事件。

ACTION_DROP

当用户在视图对象上释放拖动阴影时,该视图对象的拖动事件监听器就会接收到这个类型的拖动事件。这个操作类型只会发送给在回应ACTION_DRAG_STARTED类型的拖动事件中返回true的那个视图对象的监听器。如果用户释放拖动阴影的那个视图没有注册监听器,或者用户在当前布局之外的任何对象上释放了拖动阴影,那么这个操作类型就不会被发送。

如果释放动作顺利,监听器应该返回true,否则应该返回false。

ACTION_DRAG_ENDED

当系统结束拖动动作时,视图对象的拖动事件监听器就会接收到这个类型的拖动事件。这种操作类型不一定是前面有一个ACTION_DROP事件。如果系统发送一个ACTION_DROP,并接收到一个ACTION_DRAG_ENDED操作类型,并不意味着拖动事件的成功。监听器必须调用getResult()方法来获取在回应ACTION_DROP事件中返回的结果。如果ACTION_DROP事件没有被发送,那么getResult()就返回false。

 

表10-1 DragEvent的事件类型

getAction()

getClipDescription()

getLocalState()

getX()

getY()

getClipData()

getResult()

ACTION_DRAG_STARTED

X

X

X

 

 

 

ACTION_DRAG_ENTERED

X

X

X

X

 

 

ACTION_DRAG_LOCATION

X

X

X

X

 

 

ACTION_DRAG_EXITED

X

X

 

 

 

 

ACTION_DROP

X

X

X

X

X

 

ACTION_DRAG_ENDED

X

X

 

 

 

 X

表10-2 有效利用拖放事件数据集的操作类型

如果一个方法不包含对某个特定的操作类型有效的数据,那么就会根据该方法的返回值类型返回null或0.

10.1.4拖动阴影

在拖动过程中,系统会显示一张用户拖动的图片。对数据移动而言,这张图片代表着那些正在被移动的数据。对其他操作而言,这张图片代表着拖动操作的某些环节。这张图片就被叫做是一个拖动阴影。你可以通过你声明的View.DragShadowBuilder对象的方法去创建它,然后当你使用startDrag()方法的一部分,系统调用你定义的View.DragShadowBuilder里面的回调方法去获取一个拖动阴影。


View.DragShadowBuilder类有两个构造函数:

View.DragShadowBuilder(View)

这个构造函数接受你的应用程序中的任意一个视图对象。它将视图对象存储View.DragShadowBuilder对象中,因此在回调过程中,你可以去访问这个构造方法,为你构造一个拖动阴影。构造方法不必和用户选择开始一个拖动的视图对象(如果有的话)相关联。

如果你使用这个构造方法,不必去继承View.DragShadowBuilder类或覆盖它的方法。默认情况下,你会得到一个与你作为参数传递的那个视图有相同外表的拖动阴影,并且该拖动阴影会居中位于用户接触的屏幕上。

View.DragShadowBuilder()

如果你使用这个构造方法,在View.DragShadowBuilder 对象中没有一个视图对象是有效的(这个字段被设置为null)。如果使用该构造函数,你不必继承View.DragShadowBuilder类或覆盖它的方法,你可以得到一个不可见的的拖动阴影。系统不会给出一个错误。


View.DragShadowBuilder类有两个方法:

onProvideShadowMetrics()

系统在调用了startDrag()这个方法之后,立刻回调用这个方法。用这个方法给系统发送拖动阴影的规模和接触点。这个方法有两个参数:

dimensions

一个Point对象。拖动阴影的宽为x,高为y。

touch_point

一个Point对象。这个接触点应该是拖动过程中,在用户手指之下的拖动阴影的位置。它的X轴坐标为x,Y轴坐标为y。

onDrawShadow()

在调用了onProvideShadowMetrics()方法之后,系统立刻调用onDrawShadow()这个方法来获取拖动阴影。这个方法只有一个参数,一个Canvas对象,该对象是系统利用你提供给onProvideShadowMetrics()方法里面的参数构造出来的。利用它可以在提供给你的Canvas对象中绘制你的拖动阴影。

10.2 设计一个拖放操作

10.2.1开始一个拖动

用户用一个拖动的手势开始一个拖动,通常是一个在视图对象上的长按动作。作为回应,应做到以下几点:

1. 必要时,为那些已经移动的数据创建一个ClipData和ClipData.Item对象。作为ClipData这个对象的一部分,在ClipData中提供存储在ClipDescription对象中的元数据。因为一个拖放动作不能代表数据的移动,你可能想要使用null来代替一个实际的数据。

比如:下面代码清单10-1说明了如何通过创建一个包含了ImageView的标志或标签的ClipData对象,来回应在ImageView上的一个长按动作。以下就是这些片段,代码清单10-2说明了如何重写View.DragShadowBuilder这个类中的方法。

private static final String IMAGEVIEW_TAG = "icon bitmap"
 
ImageView imageView = new ImageView(this);
 
imageView.setImageBitmap(mIconBitmap);
 
imageView.setTag(IMAGEVIEW_TAG);
 
    ...
 
imageView.setOnLongClickListener(new View.OnLongClickListener() {
 
       public boolean onLongClick(View v) {  
    ClipData.Item item = new ClipData.Item(v.getTag()); 
    ClipData dragData = new ClipData(v.getTag(),ClipData.MIMETYPE_TEXT_PLAIN,item); 
    View.DragShadowBuilder myShadow = new MyDragShadowBuilder(imageView);
            v.startDrag(dragData, myShadow, null, 0);
 
    }
}

 

代码清单10-1

下面这个代码清单10-2定义了myDragShadowBuilder 。它为拖动一个TextView创建了一个小的灰色矩形框拖动阴影。

private static class MyDragShadowBuilder extends View.DragShadowBuilder {
 
       private static Drawable shadow;
 
             public MyDragShadowBuilder(View v) {
 
                       super(v);
 
            
            shadow = new ColorDrawable(Color.LTGRAY);
        }
 
        @Override
        public void onProvideShadowMetrics (Point size, Point touch)
          private int width, height;
         width = getView().getWidth() / 2;
            height = getView().getHeight() / 2;
            shadow.setBounds(0, 0, width, height);
            size.set(width, height);
            touch.set(width / 2, height / 2);
        }
 
        @Override
        public void onDrawShadow(Canvas canvas) {
            shadow.draw(canvas);
        }
    }

 

代码清单10-2

注意:记住你不必去继承View.DragShadowBuilder。构造方法View.DragShadowBuilder(View)会创建一个默认的拖动阴影,这个拖动阴影与传递给它的View参数一样大,并且位于以接触点为中心的位置。

10.2.2响应一个拖动开始操作

在拖动过程中,系统将拖动事件分配给当前布局中的视图对象的拖动事件监听器。监听器应该调用getAction()这个方法获取操作类型。在一个拖动开始时,这个方法返回ACTION_DRAG_STARTED。

作为回应一个操作类型为 ACTION_DRAG_STARTED的事件,监听器应该做到以下几点:

1.调用getClipDescription()方法获取ClipDescription。使用在ClipDescription中的MIME类型的方法查看监听器是否接收被拖动的数据。 如果拖放操作没有代表数据的移动,那么这个步骤就不是必须的。

2.如果监听器可以接收一个拖动,它必须返回true。这样会告诉系统继续发送拖动事件给监听器。如果监听器不接收一个拖动,就会返回false,系统就会停止发送拖动事件直到它发送ACTION_DRAG_ENDED。

注意对于ACTION_DRAG_STARTED事件,以下这些DragEvent的方法都是无效的:getClipData()、 getX()、 getY()和getResult()。

10.2.3在拖动过程中处理事件

在拖动过程中,作为回应ACTION_DRAG_STARTED拖动事件,监听器返回true来继续接受拖动事件。监听器在拖动过程中接收到的拖动事件类型取决于拖放阴影的位置以及监听器视图的可见性。

在拖动过程中,监听器首先使用拖动事件来决定是否应该改变他们的视图的外观。

在拖动过程中,getAction()返回以下三个变量中的一个:

◆ACTION_DRAG_ENTERED:当接触点(屏幕上位于用户手指下的那个点)进入监听器的视图的边界框范围内时监听器会接收到这个事件。

◆ACTION_DRAG_LOCATION:一旦监听器接收到ACTION_DRAG_LOCATION事件,在它接收到ACTION_DRAG_EXITED事件之前,接触点每移动一次,它都会接收到一个新的ACTION_DRAG_LOCATION事件。方法getX()和getY()会返回接触点的X轴和Y轴的坐标。

◆ACTION_DRAG_EXITED:在拖动阴影不再位于监听器视图的边界框范围之内时,这个事件会被发送给以前接收到ACTION_DRAG_ENTERED事件的监听器。

监听器不必对这些操作类型中的任意一个作出反应。如果监听器返回一个值给系统,它会被忽略掉。下面是应对这些动作类型的一些准则:

◆在回应ACTION_DRAG_ENTERED或者ACTION_DRAG_LOCATION时,监听器可以通过改变视图的外观来表明它将要接收到一个拖动。

◆具有ACTION_DRAG_LOCATION操作类型的事件包含了对getX()和getY()方法有效的数据,相应的接触点的位置。监听器可能可以使用这些信息来改变在接触点的视图的部分的外观。监听器也可以用这些信息来决定用户想要释放拖动阴影的精确位置。

◆在响应ACTION_DRAG_EXITED时,监听器应该重置它在回应ACTION_DRAG_ENTERED或ACTION_DRAG_LOCATION中应用的任何外观的变化。这是在向用户表明视图不再是一个临近被释放的目标。

10.2.4响应释放操作

当用户在应用程序的视图上释放拖动阴影时,并且该视图会事先报告是否可以接收被拖动的内容,系统将拖动事件分发给那个含有 ACTION_DROP操作类型的视图。监听器应做到以下几点:

1.调用getClipData()方法获取最初在startDrag()方法中应用的ClipData对象,并储存之。如果拖放操作没有代表数据的移动,这些都不是必须的。

2.监听器应返回true来表明释放动作已顺利完成,如果没有完成的话,则返回false。这个被返回的值成为ACTION_DRAG_ENDED事件中getResult()方法的返回值。

需要注意的是,如果系统没有发送出ACTION_DROP事件,那么ACTION_DRAG_ENDED事件中getResult()方法的返回值就为false。

对于ACTION_DROP事件来说,在释放动作的瞬间,getX()和getY()方法使用接收释放动作的视图上的坐标系统,返回拖动点的X轴和Y轴的坐标。

系统允许用户在监听器不接收拖动事件的视图上释放拖动阴影。系统允许用户在应用程序UI的空区域或者应用程序之外的区域释放拖动阴影。在以上例子中,系统虽然会发送ACTION_DRAG_ENDED事件,但是不会发送一个ACTION_DROP事件。

10.2.5响应一个拖动的结束操作

用户释放了拖动阴影后,系统会立即给应用程序中所有的拖动事件监听器发送ACTION_DRAG_ENDED类型的拖动事件,表明拖动动作结束了。

每个监听器都应该做下列事情:

1.如果监听器在操作期间改变了View对象的外观,那么应该把View对象重置为默认的外观。这是对用户可见的操作结束的指示.

2.监听器能够可选的调用getResult()方法来查找更多的相关操作。如果在响应ACTION_DROP类型的事件中监听器返回了true,那么getResult()方法也会返回true。在其他的情况中,getResult()方法会返回false,包括系统没有发出ACTION_DROP事件的情况.

3.监听器应该给系统返回true。

10.2.6响应拖动事件的例子

所有的拖动事件都会被拖动事件的回调方法或监听器所接收。以下代码清单10-3是一个简单的在监听器中对拖动事件作出反应的示例。

mDragListen = new myDragEventListener();
 
View imageView = new ImageView(this);
imageView.setOnDragListener(mDragListen);
...
protected class myDragEventListener implements View.OnDragEventListener {
    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)) {
                    v.setColorFilter(Color.BLUE);
                    v.invalidate();
                    return(true);
                    } else {
                    return(false);
                    }
                break;
 
            case DragEvent.ACTION_DRAG_ENTERED: {
                v.setColorFilter(Color.GREEN);
                v.invalidate();
                return(true);
                break;
 
                case DragEvent.ACTION_DRAG_LOCATION:
                return(true);
                break;
 
                case DragEvent.ACTION_DRAG_EXITED:
                 v.setColorFilter(Color.BLUE);
                    v.invalidate();
                    return(true);
                break;
 
                case DragEvent.ACTION_DROP:
                    ClipData.Item item = event.getClipData().getItemAt(0);
                    dragData = item.getText();
                    Toast.makeText(this, "Dragged data is " + dragData, Toast.LENGTH_LONG);
                    v.clearColorFilter();
                    v.invalidate();
                    return(true);
                break;
 
                case DragEvent.ACTION_DRAG_ENDED:
                    v.clearColorFilter();
                    v.invalidate();
                    if (event.getResult()) {
                        Toast.makeText(this, "The drop was handled.", Toast.LENGTH_LONG);
                    } else {
                        Toast.makeText(this, "The drop didn't work.", Toast.LENGTH_LONG);
 
                    };
                    return(true);
                break;
                default:
                    Log.e("DragDrop Example","Unknown action type received by OnDragListener.");
                break;
        };
    };
};

 

代码清单10-3

  本文来自jy02432443,是本人辛辛苦苦一个个字码出来的,转载请保留出处,并保留追究法律责任的权利 QQ78117253 

posted @ 2013-12-23 11:11  jy02432443  阅读(1559)  评论(0编辑  收藏  举报