Android 拖拽怎么实现?Drag and Drop 操作详解

文章导读
上一个 测验 下一个 Android 拖放框架允许用户使用图形化的拖放手势,将数据从当前布局中的一个 View 移动到另一个 View。从 API 11 开始,支持将 view 拖放到其他 view 或 view group 上。该框架包含以下三个重要组件来支持拖放功能 −
📋 目录
  1. 拖放过程
  2. DragEvent 类
  3. 监听拖放事件
  4. 启动拖放事件
  5. 示例
A A

Android - 拖放



上一个
测验
下一个

Android 拖放框架允许用户使用图形化的拖放手势,将数据从当前布局中的一个 View 移动到另一个 View。从 API 11 开始,支持将 view 拖放到其他 view 或 view group 上。该框架包含以下三个重要组件来支持拖放功能 −

  • 拖放事件类

  • 拖放监听器

  • 辅助方法和类

拖放过程

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

  • 开始 − 当您在布局中开始拖动项目时发生此事件,您的应用调用 startDrag() 方法通知系统开始拖放操作。startDrag() 方法中的参数提供要拖放的数据、该数据的元数据,以及用于绘制拖放阴影的回调。

    系统首先回调到您的应用以获取拖放阴影。然后在设备上显示拖放阴影。

    接下来,系统向当前布局中所有 View 对象的已注册拖放事件监听器发送带有动作类型 ACTION_DRAG_STARTED 的拖放事件。

    要继续接收拖放事件(包括可能的 drop 事件),拖放事件监听器必须返回 true。如果拖放事件监听器返回 false,则在系统发送带有动作类型 ACTION_DRAG_ENDED 的拖放事件之前,它不会接收当前操作的拖放事件。

  • 继续 − 用户继续拖动。系统向拖动点进入的 View 的已注册拖放事件监听器发送 ACTION_DRAG_ENTERED 动作,随后发送 ACTION_DRAG_LOCATION 动作。监听器可以选择更改其 View 对象的外观以响应事件,或者通过高亮显示其 View 来反应。

    当用户将拖放阴影移出 View 的边界框后,拖放事件监听器会接收到 ACTION_DRAG_EXITED 动作。

  • 放置 − 用户在某个 View 的边界框内释放拖动的项目。系统向该 View 对象的监听器发送带有动作类型 ACTION_DROP 的拖放事件。

  • 结束 − 就在动作类型 ACTION_DROP 之后,系统发送带有动作类型 ACTION_DRAG_ENDED 的拖放事件,以指示拖放操作已结束。

DragEvent 类

DragEvent 表示系统在拖放操作的各个阶段发送的事件。此类提供了少量常量和我们在拖放过程中使用的关键方法。

常量

以下是作为 DragEvent 类一部分的所有常量整数。

序号 常量及描述
1

ACTION_DRAG_STARTED

表示拖放操作开始的信号。

2

ACTION_DRAG_ENTERED

向 View 发送信号,表示拖放点已进入 View 的边界框。

3

ACTION_DRAG_LOCATION

在 ACTION_DRAG_ENTERED 之后发送给 View,如果拖放阴影仍位于 View 对象的边界框内。

4

ACTION_DRAG_EXITED

表示用户将拖放阴影移出 View 的边界框。

5

ACTION_DROP

向 View 发送信号,表示用户释放了拖放阴影,且拖放点位于 View 的边界框内。

6

ACTION_DRAG_ENDED

向 View 发送信号,表示拖放操作已结束。

方法

以下是 DragEvent 类中几个重要且最常用的方法。

序号 常量及描述
1

int getAction()

检查此事件的动作值。

2

ClipData getClipData()

返回在调用 startDrag() 时发送给系统的 ClipData 对象。

3

ClipDescription getClipDescription()

返回 ClipData 中包含的 ClipDescription 对象。

4

boolean getResult()

返回拖放操作结果的指示。

5

float getX()

获取拖放点的 X 坐标。

6

float getY()

获取拖放点的 Y 坐标。

7

String toString()

返回此 DragEvent 对象的字符串表示。

监听拖放事件

如果您希望 Layout 中的某个 view 响应拖放事件,则该 view 要么实现 View.OnDragListener,要么设置 onDragEvent(DragEvent) 回调方法。当系统调用该方法或监听器时,会向其传递上面介绍的 DragEvent 对象。您可以为 View 对象同时设置监听器和回调方法。如果这样做,只要监听器返回 true,系统会先调用监听器,然后再调用定义的回调。

onDragEvent(DragEvent) 方法与 View.OnDragListener 的组合类似于旧版 Android 中用于触摸事件的 onTouchEvent()View.OnTouchListener 的组合。

启动拖放事件

首先为要移动的数据创建 ClipDataClipData.Item。作为 ClipData 对象的一部分,提供存储在 ClipData 内 ClipDescription 对象中的元数据。对于不表示数据移动的拖放操作,您可能希望使用 null 而非实际对象。

接下来,您可以扩展 View.DragShadowBuilder 来为拖动 view 创建拖放阴影,或者简单地使用 View.DragShadowBuilder(View) 来创建默认拖放阴影,该阴影大小与传递给它的 View 参数相同,触摸点位于拖放阴影的中心。

示例

以下示例展示了使用 View.setOnLongClickListener()View.setOnTouchListener()View.OnDragEventListener() 实现简单拖放功能的代码。

步骤 描述
1 您将使用 Android Studio IDE 创建一个 Android 应用程序,并将其命名为 My Application,包名为 com.example.saira_000.myapplication
2 修改 src/MainActivity.java 文件,添加代码以定义事件监听器以及示例中使用的 logo 图像的回调方法。
3 将图像 abc.png 复制到 res/drawable-* 文件夹中。如果您想为不同设备提供不同分辨率的图像,可以使用不同分辨率的图像。
4 修改布局 XML 文件 res/layout/activity_main.xml,以定义 logo 图像的默认视图。
5 运行应用程序以启动 Android 模拟器,并验证应用程序中更改的结果。

以下是修改后的主活动文件 src/MainActivity.java 的内容。该文件可以包含所有基本生命周期方法。

package com.example.saira_000.myapplication;

import android.app.Activity;

import android.content.ClipData;
import android.content.ClipDescription;

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;

import android.view.DragEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;

import android.widget.ImageView;
import android.widget.RelativeLayout;


public class MainActivity extends Activity {
   ImageView img;
   String msg;
   private android.widget.RelativeLayout.LayoutParams layoutParams;
   
   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      img=(ImageView)findViewById(R.id.imageView);
      
      img.setOnLongClickListener(new View.OnLongClickListener() {
         @Override
         public boolean onLongClick(View v) {
            ClipData.Item item = new ClipData.Item((CharSequence)v.getTag());
            String[] mimeTypes = {ClipDescription.MIMETYPE_TEXT_PLAIN};
            
            ClipData dragData = new ClipData(v.getTag().toString(),mimeTypes, item);
            View.DragShadowBuilder myShadow = new View.DragShadowBuilder(img);
            
            v.startDrag(dragData,myShadow,null,0);
            return true;
         }
      });
      
      img.setOnDragListener(new View.OnDragListener() {
         @Override
         public boolean onDrag(View v, DragEvent event) {
            switch(event.getAction()) {
               case DragEvent.ACTION_DRAG_STARTED:
               layoutParams = (RelativeLayout.LayoutParams)v.getLayoutParams();
               Log.d(msg, "Action is DragEvent.ACTION_DRAG_STARTED");
               
               // 无需执行任何操作
               break;
               
               case DragEvent.ACTION_DRAG_ENTERED:
               Log.d(msg, "Action is DragEvent.ACTION_DRAG_ENTERED");
               int x_cord = (int) event.getX();
               int y_cord = (int) event.getY();
               break;
               
               case DragEvent.ACTION_DRAG_EXITED :
               Log.d(msg, "Action is DragEvent.ACTION_DRAG_EXITED");
               x_cord = (int) event.getX();
               y_cord = (int) event.getY();
               layoutParams.leftMargin = x_cord;
               layoutParams.topMargin = y_cord;
               v.setLayoutParams(layoutParams);
               break;
               
               case DragEvent.ACTION_DRAG_LOCATION  :
               Log.d(msg, "Action is DragEvent.ACTION_DRAG_LOCATION");
               x_cord = (int) event.getX();
               y_cord = (int) event.getY();
               break;
               
               case DragEvent.ACTION_DRAG_ENDED   :
               Log.d(msg, "Action is DragEvent.ACTION_DRAG_ENDED");
               
               // 无需执行任何操作
               break;
               
               case DragEvent.ACTION_DROP:
               Log.d(msg, "ACTION_DROP event");
               
               // 无需执行任何操作
               break;
               default: break;
            }
            return true;
         }
      });
      
      img.setOnTouchListener(new View.OnTouchListener() {
         @Override
         public boolean onTouch(View v, MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
               ClipData data = ClipData.newPlainText("", "");
               View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(img);
               
               img.startDrag(data, shadowBuilder, img, 0);
               img.setVisibility(View.INVISIBLE);
               return true;
            } else {
               return false;
            }
         }
      });
   }
}

以下是 res/layout/activity_main.xml 文件的内容 −

在以下代码中,abc 表示 example.com 的 logo。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools" 
   android:layout_width="match_parent"
   android:layout_height="match_parent" 
   android:paddingLeft="@dimen/activity_horizontal_margin"
   android:paddingRight="@dimen/activity_horizontal_margin"
   android:paddingTop="@dimen/activity_vertical_margin"
   android:paddingBottom="@dimen/activity_vertical_margin" 
   tools:context=".MainActivity">
   
   <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="Drag and Drop Example"
      android:id="@+id/textView"
      android:layout_alignParentTop="true"
      android:layout_centerHorizontal="true"
      android:textSize="30dp" />
      
   <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text=""
      android:id="@+id/textView2"
      android:layout_below="@+id/textView"
      android:layout_centerHorizontal="true"
      android:textSize="30dp"
      android:textColor="#ff14be3c" />>
      
   <ImageView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:id="@+id/imageView"
      android:src="@drawable/abc"
      android:layout_below="@+id/textView2"
      android:layout_alignRight="@+id/textView2"
      android:layout_alignEnd="@+id/textView2"
      android:layout_alignLeft="@+id/textView2"
      android:layout_alignStart="@+id/textView2" />

</RelativeLayout>

以下是 res/values/strings.xml 的内容,用于定义两个新常量 −

<?xml version="1.0" encoding="utf-8"?>
<resources>
   <string name="app_name">My Application</string>
</resources>

以下是 AndroidManifest.xml 的默认内容 −

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.example.saira_000.myapplication" >
      
   <application
      android:allowBackup="true"
      android:icon="@drawable/ic_launcher"
      android:label="@string/app_name"
      android:theme="@style/AppTheme" >
      
      <activity
         android:name=".MainActivity"
         android:label="@string/app_name" >
      
         <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
         </intent-filter>
      
      </activity>
      
   </application>
</manifest>

让我们尝试运行您的 My Application 应用程序。我假设您在设置环境时已经创建了 AVD。要在 Android Studio 中运行应用,请打开项目的一个 activity 文件,然后点击工具栏中的运行 Eclipse Run Icon 图标。Android Studio 将在您的 AVD 上安装应用并启动它,如果您的设置和应用程序一切正常,它将显示以下模拟器窗口 −

Android Drag and Drop

现在对显示的 logo 进行长按,您会看到 logo 图像在长按 1 秒后从原位置稍微移动,这是您开始拖动图像的时机。您可以在屏幕上拖动它并将其放置到新位置。

Android Drop to New Location