Android开发者指南-用户界面-拖放-Drag and Drop[原创译文]

用户界面:


Drag and Drop


英文原文:http://developer.android.com/guide/topics/ui/drag-drop.html

版本:Android 4.0 r1

译者注:黄色底色为未决译文

利用Android的拖/放框架,你可以让用户用图形化的拖放手势把一个View中的数据移到当前layout内的另一个View中去。 拖放框架包括拖动事件类、拖动侦听器,以及helper方法和类。

虽然此框架主要是为转移数据而设计的,但你可以将它用于其它UI action。比如,你可以创建一个混合颜色的应用,用户可以把不同颜色的图标通过拖放叠在一起。 不过,本文描述的是转移数据这部分内容。

概述

当用户触摸出某个拖动数据的手势,即启动了拖放操作。作为响应,你的应用程序应通知系统拖动已开始。系统将回调你的应用程序,以获取拖动数据所显示的图像。 当用户手指在当前layout上方拖动这个图像(一个“拖动阴影”)时,系统会把拖动事件发送给拖动事件侦听器对象、layout内 View 对象关联的拖动事件回调方法。一旦用户放开这个拖动阴影,系统就会终止拖动操作。

你可以从 View.OnDragListener 类创建一个拖动事件侦听器对象(“listener”)。利用View对象的setOnDragListener() 方法,可为View设置拖动事件侦听器对象。View对象还有一个 onDragEvent() 回调方法。这两个方法的详细信息都在 拖动事件侦听器和回调方法 一节中描述。

注意: 为了简化起见,下文中把接收拖动事件的程序都叫做“拖动事件监听器”,尽管实际上它可能是个回调方法。

当你开始拖动时,要向系统调用传入两类信息:要转移的数据和描述这些数据的元数据。在拖动过程中,系统会把拖动事件发送给拖动事件侦听器或layout中所有View的回调方法。 此侦听器或回调方法可以利用元数据来确定是否在放下后接受数据。 如果用户在某个View对象上放下数据,并且该View对象的侦听器或回调方法之前已经通知系统愿意接受数据,则系统会把数据发送给侦听器或拖动事件中的回调方法。

通过调用 startDrag() 方法,你的应用程序可以通知系统开始拖动。这将通知系统开始发送拖动事件。此方法中还负责发送所拖动的数据。

你可以调用任何当前layout内已关联的View的 startDrag() 。系统用View对象来获得layout的全局设置参数。

当你的应用程序完成 startDrag() 调用后,剩下的步骤就是使用系统发送给当前layout中View对象的事件了。

拖/放过程

拖放过程有四个基本步骤或状态:

启动Started
为了响应用户开始拖动的手势,你的应用程序应调用 startDrag() 来通知系统。 startDrag() 的参数需要指定所拖动的数据、元数据和绘制拖动阴影的回调方法。

作为响应,系统首先通过回调应用程序来获取拖动阴影。然后在设备上显示这个阴影。

下一步,系统会把一个带有action类型 ACTION_DRAG_STARTED 的拖动事件发送给当前layout中所有View对象的拖动事件侦听器。 为了能继续接收拖动事件,包括可能发生的放下事件,拖动事件侦听器必须返回true。 这会向系统注册侦听器。只有注册过的侦听器才能连续接收拖动事件。这时,侦听器还可以改变所属View对象的外观,以便显示出该侦听器可以接受放下事件。

如果拖动事件侦听器返回false,则在系统发送带有action类型 ACTION_DRAG_ENDED 的拖动事件之前,它就再不会收到当前操作的拖动事件了。 通过返回false,侦听器通知系统,它对此拖动操作不感兴趣,并且不愿意接受拖动的数据。

保持Continuing
表示用户保持拖动状态。当拖动阴影与View对象的屏幕边界相交时,系统会发送一个或多个拖动事件给View对象的拖动事件侦听器(如果已经注册同意接收事件)。 作为对事件的响应,侦听器可以选择改变View对象的外观。比如,如果事件表明拖动阴影已经进入了View的边界(action类型 ACTION_DRAG_ENTERED ),侦听器可以让View高亮显示。
放下Dropped
表示用户在某个可接受数据的View的屏幕边界内释放了拖动阴影。系统会向View对象的侦听器发送一个带有action类型 ACTION_DROP 的拖动事件。拖动事件中包含了开始拖动时由 startDrag() 传给系统的数据。如果接受放下事件执行成功,侦听器应该向系统返回布尔值true

记住,只有View的侦听器已注册接收拖动事件了,用户在此View边界内放下拖动阴影才会发生这一步。如果用户是在其它情况下释放拖动阴影,则不会发送 ACTION_DROP 拖动事件。

结束Ended
用户释放了拖动阴影,系统也已发出(必要时)带有action类型 ACTION_DROP 拖动事件之后,系统会发出一个带action类型 ACTION_DRAG_ENDED 拖动事件,用以表明拖动操作已经结束。无论用户在何处释放拖动阴影,这一步都会发生。 此事件会发送给所有注册接收拖动事件的侦听器,即使侦听器已收到过了 ACTION_DROP 事件也一样。

拖放操作的设计 一节中还会详细说明这四个步骤。

拖动事件侦听器及回调方法

一个View可以通过实现一个 View.OnDragListener 或者实现其 onDragEvent(DragEvent) 回调方法来接收拖动事件。当系统调用此方法或侦听器时,会传入一个 DragEvent 对象。

在绝大多数场合,你都会更愿意使用侦听器。当进行用户界面设计时,你通常不会建立View类的子类,可是要使用回调方法你就只能这么做,因为你要重写这个回调方法。 相比之下,你可以实现一个侦听器类,并把它用于多个不同的View对象。你也可以把侦听器实现为匿名的内嵌类。 要设置View对象的侦听器,请调用setOnDragListener() 。

你可以同时对View对象使用侦听器和回调方法。这时,系统会首先调用侦听器。只有侦听器返回false时,系统才会再去调用回调方法。

onDragEvent(DragEvent) 方法和 View.OnDragListener 的混合使用,与混用 onTouchEvent() 和触摸事件的 View.OnTouchListener 的效果类似。

拖动事件

系统以 DragEvent 对象的格式送出一个拖动事件。此对象包含了一个action类型,用于告诉侦听器拖放过程中发生的事件。根据action类型的不同,此对象中还包含了其它一些数据。

要获取action类型,侦听器调用 getAction() 即可。共有六种可能的类型,都在 DragEvent 类中用常量定义,已在表 1中列出。

DragEvent 对象还包含了应用程序在 startDrag() 调用中提交给系统的数据。某些数据仅针对特定action类型才会有。 每种action对应的可用数据都列在了table 2中。表中还详细说明了 拖放操作的设计 一节中可用的事件。

表 1. DragEvent action type

getAction() 值含义
ACTION_DRAG_STARTED 一旦应用程序调用了 startDrag() ,某个View对象的拖动事件侦听器接着就会收到该事件action类型,并且获得一个拖动阴影。
ACTION_DRAG_ENTERED 一旦拖动阴影进入了某View的屏幕边界内,此View对象的拖动事件侦听器接着就会收到该事件action类型。这是拖动阴影进入边界后,侦听器收到的第一个事件action类型。 如果侦听器期望能继续收到本次操作的拖动事件,则必须向系统返回true
ACTION_DRAG_LOCATION 当View对象的拖动事件侦听器已收到过一个 ACTION_DRAG_ENTERED 事件,且拖动阴影仍旧在本View的边界范围内时,侦听器会收到该事件action类型。
ACTION_DRAG_EXITED 当View对象的拖动事件侦听器收到过一个 ACTION_DRAG_ENTERED 事件和至少一个ACTION_DRAG_LOCATION ,并且用户已经把拖动阴影移到了View的边界之外时,侦听器将会收到该事件action类型。
ACTION_DROP 当用户在View对象上面释放拖动阴影时,View对象的拖动事件侦听器将会收到该事件action类型。仅当侦听器在 ACTION_DRAG_STARTED 返回true时,该事件action类型才会发送给View对象的拖动事件侦听器。 如果用户释放拖动阴影时下方的View没有注册侦听器,或者用户未在当前layout上释放拖动阴影,则该action类型不会发送。

如果侦听器已经成功处理了放下操作,它应该返回布尔值true。否则返回false。

ACTION_DRAG_ENDED 当系统停止拖动操作时,View对象的拖动事件侦听器将会收到该事件action类型。该action类型之前不一定是发生了 ACTION_DROP 事件。如果系统已发送了一个 ACTION_DROP ,收到action类型ACTION_DRAG_ENDED 也并不表示放下操作已经处理成功。侦听器必须调用 getResult() 来获取ACTION_DROP 的执行结果。如果没有发送过 ACTION_DROP 事件,则 getResult() 将返回false

表 2. action 类型对应的可用 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

getAction()、 describeContents()、 writeToParcel() 和 toString() 方法总是返回可用数据。

如果不包含某个action类型可用的数据,此方法会返回null或0,视结果类型而定。

拖动阴影

在拖放操作的过程中,系统会显示一个用户所拖动部分的图像。对于数据转移而言,这个图像代表了要拖动的数据。 对于其它操作,这个图像代表了被拖动的东西

此图像叫做拖动阴影(drag shadow)。你可以用 View.DragShadowBuilder 来创建它,并在用startDrag() 开始拖动时把它传给系统。 调用 startDrag() 后,系统会执行你在View.DragShadowBuilder 中定义的回调方法来获取一个拖动阴影。

View.DragShadowBuilder 类包含两个构造方法:

View.DragShadowBuilder(View)
这个构造方法可接受你的应用程序中的任一 View 对象。它会在 View.DragShadowBuilder 对象中保存View对象,因此构造拖动阴影时你可以在执行回调方法中访问到该View。 这样就不一定要记住用户选中并开始拖动操作的View了(如果有的话)。

如果使用了本构造方法,你就不必扩展 View.DragShadowBuilder 及重写其方法。默认情况下,你会得到一个与参数中的View外观相同的拖动阴影,并且以用户触摸点为中心来显示。

View.DragShadowBuilder()
如果你使用了本构造方法,则 View.DragShadowBuilder 对象中不存在View对象(对应的字段值为null)。 如果使用了本构造方法,且未扩展 View.DragShadowBuilder 并重写其方法,那么你将得到一个不可见的拖动阴影。且系统不会报错。

View.DragShadowBuilder 类有两个方法:

onProvideShadowMetrics()
当你调用了 startDrag() 之后,系统马上会调用本方法。可用于向系统发送拖动阴影的大小和触摸点坐标。本方法有两个参数:
dimensions
一个 Point 对象。其中的 x 和 y 对应了拖动阴影的宽和高。
touch_point
一个 Point 对象。触摸点是指用户手指在拖动过程中所触及的点在拖动阴影中的相对位置。X坐标用 x 表示,Y坐标用 y 表示。
onDrawShadow()
在调用 onProvideShadowMetrics() 之后,系统会立即调用 onDrawShadow() ,用于获取拖动阴影。本方法有一个参数,即一个 Canvas 对象,系统根据你在 onProvideShadowMetrics() 中给出的参数来构建它,并将在这个 Canvas 上绘制拖动阴影。

为了提高性能,你应该让拖动阴影尽可能小一些。对于单个目标,也许你该使用图标。如果选中了多个目标,也许你该把多个图标堆叠起来显示,而不是把整个图像显示在屏幕上。

拖放操作的设计

这一节分步展示了如何开始拖动、在拖动过程中响应事件、响应放下事件、结束拖放操作等内容。

开始拖动

用户用拖动手势来开始拖动,通常是在View对象上进行一个长按操作。在事件响应中,你应该进行:

  1. 必须创建用于移动数据的 ClipData 和 ClipData.Item 。在ClipData对象中,需要给出存放元数据的ClipDescription 对象。对于不用于转移数据的拖放操作,你可能要用null来取代实际的对象。

    比如,以下代码片段展示了如何响应ImageView上的长按操作,创建一个ClipData对象,其中包含了ImageView的tag或label。 然后,再下一段代码展示了如何重写 View.DragShadowBuilder 中的方法:

    // 创建一个字符串,用于ImageView label
    private static final String IMAGEVIEW_TAG = "icon bitmap"
    
    // 创建一个新的ImageView
    ImageView imageView = new ImageView(this);
    
    // 用某个图标(在其它地方定义)的位图设置ImageView位图图像
    imageView.setImageBitmap(mIconBitmap);
    
    // 设置tag
    imageView.setTag(IMAGEVIEW_TAG);
    
        ...
    
    // 把一个匿名侦听器对象设为ImageView的长按操作侦听器
    // 侦听器实现了OnLongClickListener接口
    imageView.setOnLongClickListener(new View.OnLongClickListener() {
    
        // 定义接口的方法,长按View时会被调用到
        public boolean onLongClick(View v) {
    
        // 新建一个ClipData
        // 这用两步即可完成,
    // ClipData.newPlainText()方法可以很方便地一步完成一个纯文本ClipData的创建工作
    
        // 用ImageView对象的tag创建一个新的ClipData.Item
        ClipData.Item item = new ClipData.Item(v.getTag());
    
        // 新建一个ClipData,用已有的tag作为label,纯文本MIME类型。
        // 这会在ClipData中新建一个ClipDescription对象,
        // 并把它的MIME类型一栏设为"text/plain"。
        ClipData dragData = new ClipData(v.getTag(),ClipData.MIMETYPE_TEXT_PLAIN,item);
    
        // 实例化drag shadow builder.
        View.DrawShadowBuilder myShadow = new MyDragShadowBuilder(imageView);
    
        // 开始拖动
    
                v.startDrag(dragData,  // 要拖动的数据
                            myShadow,  // drag shadow builder
                            null,      // 不需要用到本地数据
                            0          // 标志位(目前未启用,设为0)
                );
    
        }
    }


  2. 以下代码段定义了myDragShadowBuilder 为TextView创建一个拖动阴影,显示为一个灰色的小方框:
    private static class MyDragShadowBuilder extends View.DragShadowBuilder {
    
        // 拖动阴影图像,定义为一个drawable
        private static Drawable shadow;
    
            // 定义myDragShadowBuilder的构造方法
            public MyDragShadowBuilder(View v) {
    
                // 保存传给myDragShadowBuilder的View参数
                super(v);
    
                // 创建一个可拖动的图像,用于填满系统给出的Canvas
                shadow = new ColorDrawable(Color.LTGRAY);
            }
    
            // 定义一个回调方法,用于把拖动阴影的大小和触摸点位置返回给系统
            @Override
            public void onProvideShadowMetrics (Point size, Point touch)
                // 定义本地变量
                private int width, height;
    
                // 把阴影的宽度设为原始View的一半
                width = getView().getWidth() / 2;
    
                // 把阴影的高度设为原始View的一半
                height = getView().getHeight() / 2;
    
                // 拖动阴影是一个ColorDrawable对象。
                // 下面把它设为与系统给出的Canvas一样大小。这样,拖动阴影将会填满整个Canvas。
                shadow.setBounds(0, 0, width, height);
    
                // 设置长宽值,通过size参数返回给系统。
                size.set(width, height);
    
                // 把触摸点的位置设为拖动阴影的中心
                touch.set(width / 2, height / 2);
            }
    
            // 定义回调方法,用于在Canvas上绘制拖动阴影,Canvas由系统根据onProvideShadowMetrics()传入的尺寸参数创建。
            @Override
            public void onDrawShadow(Canvas canvas) {
    
                // 在系统传入的Canvas上绘制ColorDrawable
                shadow.draw(canvas);
            }
        }


    注意:请记住你不必扩展 View.DragShadowBuilder 。构造器View.DragShadowBuilder(View) 创建一个默认与传入的参数View大小相同的拖动阴影,其中触摸点位于拖动阴影的中心。

响应拖动开始事件

在拖动过程中,系统会向当前layout中View对象的拖动事件侦听器发送拖动事件。侦听器应该调用getAction() 来获取action类型。在开始拖动时,该方法返回 ACTION_DRAG_STARTED 。

为了响应带action类型 ACTION_DRAG_STARTED 的事件,侦听器应该完成以下工作:

  1. 调用 getClipDescription() 来获取 ClipDescription 。使用 ClipDescription 的方法来获取MIME类型,以确定侦听器是否接受拖动的数据。

    如果拖放操作不是用于转移数据的,这步可能就不需要了。

  2. 如果侦听器可以接受拖动,则应该返回true。这将告诉系统以后还要向该侦听器发送拖动事件。 如果不能接受拖动,则它应该返回false,系统将会在发出 ACTION_DRAG_ENDED 之前停止向它发送拖动事件。

请注意,对于 ACTION_DRAG_STARTED 事件而言, DragEvent 的以下方法是不可用的: getClipData()getX()、 getY()、 getResult()

拖动过程中的事件处理

在拖动过程中,响应 ACTION_DRAG_STARTED 拖动事件并返回true的侦听器将会持续接受拖动事件。 在拖动过程中,侦听器收到的拖动事件类型取决于拖动阴影的位置和侦听器所属View的可见性。

在拖动过程中,侦听器主要依据拖动事件来确定是否要修改View的外观。

在拖动过程中, getAction() 返回以下三种类型之一:

侦听器不需要对这些action类型返回值。如果侦听器向系统返回一个值,将会被忽略。下面列出了一些响应这些action类型需要遵守的规则:

  • 在对 ACTION_DRAG_ENTERED 或 ACTION_DRAG_LOCATION 的响应过程中,侦听器可以修改View的外观,以便显示出将要接受放下操作。
  • 带有action类型 ACTION_DRAG_LOCATION 的事件包含了 getX() 和 getY() 所需的数据,表示触摸点的坐标位置。侦听器可能要用这个信息来改变View上触摸点附近的外观。 侦听器还可以用此信息来确定用户将要放下拖动阴影的确切位置。
  • 在对 ACTION_DRAG_EXITED 响应过程中,侦听器应该把响应 ACTION_DRAG_ENTERED 或ACTION_DRAG_LOCATION 时对外观的改动恢复原状。这向用户表明此View不再是作为放下操作的目的地了。

响应放下事件

当用户在应用程序的某个View上释放了拖动阴影,并且这个View之前已声明它可以接受被拖动的内容,系统就会向此View发送一个带有View类型 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坐标,采用收到放下操作的View的坐标系。

系统允许用户在侦听器不接收拖动事件的View上释放拖动阴影,也允许用户在应用程序界面的空白区域上释放拖动阴影,甚至在应用程序之外的区域也可以。 在这些情况下,系统都不会发送带有action类型 ACTION_DROP 的事件,不过会发出一个 ACTION_DRAG_ENDED 事件。

响应拖动结束事件

一旦用户释放了拖动阴影,系统会立即向应用程序内所有的拖动事件侦听器发送一个拖动事件,其中附带action类型 ACTION_DRAG_ENDED 。这表明拖动操作已经结束。

所有侦听器都应完成以下工作:

  1. 如果侦听器在运行过程中修改了View对象的外观,它应该将View恢复为默认画面。这向用户显示出拖放操作已完成。
  2. 侦听器可选择调用 getResult() 来获取更多关于拖放操作的信息。如果侦听器在响应action类型为ACTION_DROP 的事件时返回true,则 getResult() 将返回布尔值true。 除此之外,包括系统未发出ACTION_DROP 事件时, getResult() 都会返回布尔值false
  3. 侦听器应该向系统返回true

响应拖动事件的示例

所有的拖动事件首先都是由你的拖动事件方法或侦听器接收的。以下代码片段简单演示了一个在侦听器中响应拖动事件的例子:

// 新建一个拖动事件侦听器
mDragListen = new myDragEventListener();

View imageView = new ImageView(this);

// 为View设置拖动事件侦听器
imageView.setOnDragListener(mDragListen);

...

protected class myDragEventListener implements View.OnDragEventListener {

    // 这是系统向侦听器发送拖动事件时将会调用的方法
    public boolean onDrag(View v, DragEvent event) {

        // 定义一个变量,用于保存收到事件的action类型
        final int action = event.getAction();

        // 处理所有需要的事件
        switch(action) {

            case DragEvent.ACTION_DRAG_STARTED:

                // 确定本View是否接受拖动数据
                if (event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {

                    // 作为例子,把View的色彩滤镜设置蓝色,表示它可以接受数据。
                  v.setColorFilter(Color.BLUE);

                    // 标明View强制用新的颜色重绘
                    v.invalidate();

                    // 返回true表示View可以接受拖动数据
                    return(true);

                    } else {

                    // 返回false。在本次拖放操作中,本View不再会收到拖放事件,除非发出了ACTION_DRAG_ENDED。
                    return(false);

                    }
                break;

            case DragEvent.ACTION_DRAG_ENTERED: {

                // 把View的色彩滤镜设置为绿色。返回true,但返回值将被忽略。

                v.setColorFilter(Color.GREEN);

                // 表明本View强制用新的颜色重绘
                v.invalidate();

                return(true);

                break;

                case DragEvent.ACTION_DRAG_LOCATION:

                // 忽略事件
                    return(true);

                break;

                case DragEvent.ACTION_DRAG_EXITED:

                    // 重新设置为蓝色。返回true,但返回值将被忽略。
                    v.setColorFilter(Color.BLUE);

                    // 让view失效,以便强制用新的颜色重绘。
                    v.invalidate();

                    return(true);

                break;

                case DragEvent.ACTION_DROP:

                    // 获取包含拖动数据的item
                    ClipData.Item item = event.getClipData().getItemAt(0);

                    // 从数据项中获取文本数据
                    dragData = item.getText();

                    // 显示一个包含拖动数据的信息
                    Toast.makeText(this, "Dragged data is " + dragData, Toast.LENGTH_LONG);

                    // 去除所有色彩滤镜
                    v.clearColorFilter();

                    // 让view失效,以便强制用新的颜色重绘。
                    v.invalidate();

                    // 返回true。 DragEvent.getResult()也将返回true.
                    return(true);

                break;

                case DragEvent.ACTION_DRAG_ENDED:

                    // 去除所有色彩滤镜
                    v.clearColorFilter();

                    // 让view失效,以便强制用新的颜色重绘。
                    v.invalidate();

                    // 执行getResult(),显示操作的结果。
                    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);

                    };

                    // 返回true; 返回值将被忽略
                    return(true);

                break;

                // 收到一个未知的action type
                default:
                    Log.e("DragDrop Example","Unknown action type received by OnDragListener.");

                break;
        };
    };
};


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值